UNPKG

strong-arc

Version:

A visual suite for the StrongLoop API Platform

1,451 lines (1,264 loc) 73 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. */ /** * @constructor * @extends {WebInspector.VBox} * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate * @param {!WebInspector.HeapProfileHeader} profile */ WebInspector.HeapSnapshotView = function(dataDisplayDelegate, profile) { WebInspector.VBox.call(this); this.element.classList.add("heap-snapshot-view"); profile.profileType().addEventListener(WebInspector.HeapSnapshotProfileType.SnapshotReceived, this._onReceiveSnapshot, this); profile.profileType().addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this); if (profile.profileType().id === WebInspector.TrackingHeapSnapshotProfileType.TypeId) { this._trackingOverviewGrid = new WebInspector.HeapTrackingOverviewGrid(profile); this._trackingOverviewGrid.addEventListener(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, this._onIdsRangeChanged.bind(this)); } this._splitView = new WebInspector.SplitView(false, true, "heapSnapshotSplitViewState", 200, 200); this._splitView.show(this.element); this._containmentView = new WebInspector.VBox(); this._containmentView.setMinimumSize(50, 25); this._containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid(dataDisplayDelegate); this._containmentDataGrid.show(this._containmentView.element); this._containmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); this._statisticsView = new WebInspector.HeapSnapshotStatisticsView(); this._constructorsView = new WebInspector.VBox(); this._constructorsView.setMinimumSize(50, 25); this._constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid(dataDisplayDelegate); this._constructorsDataGrid.show(this._constructorsView.element); this._constructorsDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); this._diffView = new WebInspector.VBox(); this._diffView.setMinimumSize(50, 25); this._diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid(dataDisplayDelegate); this._diffDataGrid.show(this._diffView.element); this._diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); if (profile._hasAllocationStacks) { this._allocationView = new WebInspector.VBox(); this._allocationView.setMinimumSize(50, 25); this._allocationDataGrid = new WebInspector.AllocationDataGrid(profile.target() , dataDisplayDelegate); this._allocationDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._onSelectAllocationNode, this); this._allocationDataGrid.show(this._allocationView.element); this._allocationStackView = new WebInspector.HeapAllocationStackView(profile.target()); this._allocationStackView.setMinimumSize(50, 25); this._tabbedPane = new WebInspector.TabbedPane(); this._tabbedPane.closeableTabs = false; this._tabbedPane.headerElement().classList.add("heap-object-details-header"); } this._retainmentView = new WebInspector.VBox(); this._retainmentView.setMinimumSize(50, 21); this._retainmentView.element.classList.add("retaining-paths-view"); var splitViewResizer; if (this._allocationStackView) { this._tabbedPane = new WebInspector.TabbedPane(); this._tabbedPane.closeableTabs = false; this._tabbedPane.headerElement().classList.add("heap-object-details-header"); this._tabbedPane.appendTab("retainers", WebInspector.UIString("Retainers"), this._retainmentView); this._tabbedPane.appendTab("allocation-stack", WebInspector.UIString("Allocation stack"), this._allocationStackView); splitViewResizer = this._tabbedPane.headerElement(); this._objectDetailsView = this._tabbedPane; } else { var retainmentViewHeader = document.createElementWithClass("div", "heap-snapshot-view-resizer"); var retainingPathsTitleDiv = retainmentViewHeader.createChild("div", "title"); var retainingPathsTitle = retainingPathsTitleDiv.createChild("span"); retainingPathsTitle.textContent = WebInspector.UIString("Retainers"); this._retainmentView.element.appendChild(retainmentViewHeader); splitViewResizer = retainmentViewHeader; this._objectDetailsView = this._retainmentView; } this._splitView.hideDefaultResizer(); this._splitView.installResizer(splitViewResizer); this._retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid(dataDisplayDelegate); this._retainmentDataGrid.show(this._retainmentView.element); this._retainmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this); this._retainmentDataGrid.reset(); this._perspectives = []; this._perspectives.push(new WebInspector.HeapSnapshotView.SummaryPerspective()); if (profile.profileType() !== WebInspector.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType) this._perspectives.push(new WebInspector.HeapSnapshotView.ComparisonPerspective()); this._perspectives.push(new WebInspector.HeapSnapshotView.ContainmentPerspective()); if (this._allocationView) this._perspectives.push(new WebInspector.HeapSnapshotView.AllocationPerspective()); this._perspectives.push(new WebInspector.HeapSnapshotView.StatisticsPerspective()); this._perspectiveSelect = new WebInspector.StatusBarComboBox(this._onSelectedPerspectiveChanged.bind(this)); for (var i = 0; i < this._perspectives.length; ++i) this._perspectiveSelect.createOption(this._perspectives[i].title()); this._profile = profile; this._baseSelect = new WebInspector.StatusBarComboBox(this._changeBase.bind(this)); this._baseSelect.visible = false; this._updateBaseOptions(); this._filterSelect = new WebInspector.StatusBarComboBox(this._changeFilter.bind(this)); this._filterSelect.visible = false; this._updateFilterOptions(); this._classNameFilter = new WebInspector.StatusBarInput("Class filter"); this._classNameFilter.visible = false; this._constructorsDataGrid.setNameFilter(this._classNameFilter); this._diffDataGrid.setNameFilter(this._classNameFilter); this._selectedSizeText = new WebInspector.StatusBarText(""); this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true); this._currentPerspectiveIndex = 0; this._currentPerspective = this._perspectives[0]; this._currentPerspective.activate(this); this._dataGrid = this._currentPerspective.masterGrid(this); this._refreshView(); } /** * @constructor * @param {string} title */ WebInspector.HeapSnapshotView.Perspective = function(title) { this._title = title; } WebInspector.HeapSnapshotView.Perspective.prototype = { /** * @param {!WebInspector.HeapSnapshotView} heapSnapshotView */ activate: function(heapSnapshotView) { }, /** * @param {!WebInspector.HeapSnapshotView} heapSnapshotView */ deactivate: function(heapSnapshotView) { heapSnapshotView._baseSelect.visible = false; heapSnapshotView._filterSelect.visible = false; heapSnapshotView._classNameFilter.visible = false; if (heapSnapshotView._trackingOverviewGrid) heapSnapshotView._trackingOverviewGrid.detach(); if (heapSnapshotView._allocationView) heapSnapshotView._allocationView.detach(); if (heapSnapshotView._statisticsView) heapSnapshotView._statisticsView.detach(); heapSnapshotView._splitView.detach(); heapSnapshotView._splitView.detachChildViews(); }, /** * @param {!WebInspector.HeapSnapshotView} heapSnapshotView * @return {?WebInspector.DataGrid} */ masterGrid: function(heapSnapshotView) { return null; }, /** * @return {string} */ title: function() { return this._title; }, /** * @return {boolean} */ supportsSearch: function() { return false; } } /** * @constructor * @extends {WebInspector.HeapSnapshotView.Perspective} */ WebInspector.HeapSnapshotView.SummaryPerspective = function() { WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Summary")); } WebInspector.HeapSnapshotView.SummaryPerspective.prototype = { /** * @override * @param {!WebInspector.HeapSnapshotView} heapSnapshotView */ activate: function(heapSnapshotView) { heapSnapshotView._constructorsView.show(heapSnapshotView._splitView.mainElement()); heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement()); heapSnapshotView._splitView.show(heapSnapshotView.element); heapSnapshotView._filterSelect.visible = true; heapSnapshotView._classNameFilter.visible = true; if (heapSnapshotView._trackingOverviewGrid) { heapSnapshotView._trackingOverviewGrid.show(heapSnapshotView.element, heapSnapshotView._splitView.element); heapSnapshotView._trackingOverviewGrid.update(); heapSnapshotView._trackingOverviewGrid._updateGrid(); } }, /** * @override * @param {!WebInspector.HeapSnapshotView} heapSnapshotView * @return {?WebInspector.DataGrid} */ masterGrid: function(heapSnapshotView) { return heapSnapshotView._constructorsDataGrid; }, /** * @override * @return {boolean} */ supportsSearch: function() { return true; }, __proto__: WebInspector.HeapSnapshotView.Perspective.prototype } /** * @constructor * @extends {WebInspector.HeapSnapshotView.Perspective} */ WebInspector.HeapSnapshotView.ComparisonPerspective = function() { WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Comparison")); } WebInspector.HeapSnapshotView.ComparisonPerspective.prototype = { /** * @override * @param {!WebInspector.HeapSnapshotView} heapSnapshotView */ activate: function(heapSnapshotView) { heapSnapshotView._diffView.show(heapSnapshotView._splitView.mainElement()); heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement()); heapSnapshotView._splitView.show(heapSnapshotView.element); heapSnapshotView._baseSelect.visible = true; heapSnapshotView._classNameFilter.visible = true; }, /** * @override * @param {!WebInspector.HeapSnapshotView} heapSnapshotView * @return {?WebInspector.DataGrid} */ masterGrid: function(heapSnapshotView) { return heapSnapshotView._diffDataGrid; }, /** * @override * @return {boolean} */ supportsSearch: function() { return true; }, __proto__: WebInspector.HeapSnapshotView.Perspective.prototype } /** * @constructor * @extends {WebInspector.HeapSnapshotView.Perspective} */ WebInspector.HeapSnapshotView.ContainmentPerspective = function() { WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Containment")); } WebInspector.HeapSnapshotView.ContainmentPerspective.prototype = { /** * @override * @param {!WebInspector.HeapSnapshotView} heapSnapshotView */ activate: function(heapSnapshotView) { heapSnapshotView._containmentView.show(heapSnapshotView._splitView.mainElement()); heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement()); heapSnapshotView._splitView.show(heapSnapshotView.element); }, /** * @override * @param {!WebInspector.HeapSnapshotView} heapSnapshotView * @return {?WebInspector.DataGrid} */ masterGrid: function(heapSnapshotView) { return heapSnapshotView._containmentDataGrid; }, __proto__: WebInspector.HeapSnapshotView.Perspective.prototype } /** * @constructor * @extends {WebInspector.HeapSnapshotView.Perspective} */ WebInspector.HeapSnapshotView.AllocationPerspective = function() { WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Allocation")); this._allocationSplitView = new WebInspector.SplitView(false, true, "heapSnapshotAllocationSplitViewState", 200, 200); var resizer = document.createElementWithClass("div", "heap-snapshot-view-resizer"); var title = resizer.createChild("div", "title").createChild("span"); title.textContent = WebInspector.UIString("Live objects"); this._allocationSplitView.hideDefaultResizer(); this._allocationSplitView.installResizer(resizer); this._allocationSplitView.sidebarElement().appendChild(resizer); } WebInspector.HeapSnapshotView.AllocationPerspective.prototype = { /** * @override * @param {!WebInspector.HeapSnapshotView} heapSnapshotView */ activate: function(heapSnapshotView) { heapSnapshotView._allocationView.show(this._allocationSplitView.mainElement()); heapSnapshotView._constructorsView.show(heapSnapshotView._splitView.mainElement()); heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement()); heapSnapshotView._splitView.show(this._allocationSplitView.sidebarElement()); this._allocationSplitView.show(heapSnapshotView.element); heapSnapshotView._constructorsDataGrid.clear(); var selectedNode = heapSnapshotView._allocationDataGrid.selectedNode; if (selectedNode) heapSnapshotView._constructorsDataGrid.setAllocationNodeId(selectedNode.allocationNodeId()); }, /** * @override * @param {!WebInspector.HeapSnapshotView} heapSnapshotView */ deactivate: function(heapSnapshotView) { this._allocationSplitView.detach(); WebInspector.HeapSnapshotView.Perspective.prototype.deactivate.call(this, heapSnapshotView); }, /** * @override * @param {!WebInspector.HeapSnapshotView} heapSnapshotView * @return {?WebInspector.DataGrid} */ masterGrid: function(heapSnapshotView) { return heapSnapshotView._allocationDataGrid; }, __proto__: WebInspector.HeapSnapshotView.Perspective.prototype } /** * @constructor * @extends {WebInspector.HeapSnapshotView.Perspective} */ WebInspector.HeapSnapshotView.StatisticsPerspective = function() { WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Statistics")); } WebInspector.HeapSnapshotView.StatisticsPerspective.prototype = { /** * @override * @param {!WebInspector.HeapSnapshotView} heapSnapshotView */ activate: function(heapSnapshotView) { heapSnapshotView._statisticsView.show(heapSnapshotView.element); }, /** * @override * @param {!WebInspector.HeapSnapshotView} heapSnapshotView * @return {?WebInspector.DataGrid} */ masterGrid: function(heapSnapshotView) { return null; }, __proto__: WebInspector.HeapSnapshotView.Perspective.prototype } WebInspector.HeapSnapshotView.prototype = { _refreshView: function() { this._profile.load(profileCallback.bind(this)); /** * @param {!WebInspector.HeapSnapshotProxy} heapSnapshotProxy * @this {WebInspector.HeapSnapshotView} */ function profileCallback(heapSnapshotProxy) { heapSnapshotProxy.getStatistics(this._gotStatistics.bind(this)); var list = this._profiles(); var profileIndex = list.indexOf(this._profile); this._baseSelect.setSelectedIndex(Math.max(0, profileIndex - 1)); this._dataGrid.setDataSource(heapSnapshotProxy); if (this._trackingOverviewGrid) this._trackingOverviewGrid._updateGrid(); } }, /** * @param {!WebInspector.HeapSnapshotCommon.Statistics} statistics */ _gotStatistics: function(statistics) { this._statisticsView.setTotal(statistics.total); this._statisticsView.addRecord(statistics.code, WebInspector.UIString("Code"), "#f77"); this._statisticsView.addRecord(statistics.strings, WebInspector.UIString("Strings"), "#5e5"); this._statisticsView.addRecord(statistics.jsArrays, WebInspector.UIString("JS Arrays"), "#7af"); this._statisticsView.addRecord(statistics.native, WebInspector.UIString("Typed Arrays"), "#fc5"); this._statisticsView.addRecord(statistics.total, WebInspector.UIString("Total")); }, _onIdsRangeChanged: function(event) { var minId = event.data.minId; var maxId = event.data.maxId; this._selectedSizeText.setText(WebInspector.UIString("Selected size: %s", Number.bytesToString(event.data.size))); if (this._constructorsDataGrid.snapshot) this._constructorsDataGrid.setSelectionRange(minId, maxId); }, get statusBarItems() { var result = [this._perspectiveSelect.element, this._classNameFilter.element]; if (this._profile.profileType() !== WebInspector.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType) result.push(this._baseSelect.element, this._filterSelect.element); result.push(this._selectedSizeText.element); return result; }, wasShown: function() { // FIXME: load base and current snapshots in parallel this._profile.load(profileCallback.bind(this)); /** * @this {WebInspector.HeapSnapshotView} */ function profileCallback() { this._profile._wasShown(); if (this._baseProfile) this._baseProfile.load(function() { }); } }, willHide: function() { this._currentSearchResultIndex = -1; this._popoverHelper.hidePopover(); if (this.helpPopover && this.helpPopover.isShowing()) this.helpPopover.hide(); }, 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 = []; }, /** * @param {string} query * @param {function(!WebInspector.View, number)} finishedCallback */ 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) return; if (!this._currentPerspective.supportsSearch()) return; /** * @param {boolean} found * @this {WebInspector.HeapSnapshotView} */ function didHighlight(found) { finishedCallback(this, found ? 1 : 0); } if (query.charAt(0) === "@") { var snapshotNodeId = parseInt(query.substring(1), 10); if (!isNaN(snapshotNodeId)) this._dataGrid.highlightObjectByHeapSnapshotId(String(snapshotNodeId), didHighlight.bind(this)); else finishedCallback(this, 0); return; } this._searchFinishedCallback = finishedCallback; var nameRegExp = createPlainTextSearchRegex(query, "i"); function matchesByName(gridNode) { return ("_name" in gridNode) && nameRegExp.test(gridNode._name); } function matchesQuery(gridNode) { delete gridNode._searchMatched; if (matchesByName(gridNode)) { gridNode._searchMatched = true; gridNode.refresh(); return true; } return false; } var current = this._dataGrid.rootNode().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); }, /** * @return {boolean} */ showingFirstSearchResult: function() { return (this._currentSearchResultIndex === 0); }, /** * @return {boolean} */ showingLastSearchResult: function() { return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); }, /** * @return {number} */ currentSearchResultIndex: function() { return this._currentSearchResultIndex; }, _jumpToSearchResult: function(index) { var searchResult = this._searchResults[index]; if (!searchResult) return; var node = searchResult.node; node.revealAndSelect(); }, refreshVisibleData: function() { if (!this._dataGrid) return; var child = this._dataGrid.rootNode().children[0]; while (child) { child.refresh(); child = child.traverseNextNode(false, null, true); } }, _changeBase: function() { if (this._baseProfile === this._profiles()[this._baseSelect.selectedIndex()]) return; this._baseProfile = this._profiles()[this._baseSelect.selectedIndex()]; var dataGrid = /** @type {!WebInspector.HeapSnapshotDiffDataGrid} */ (this._dataGrid); // Change set base data source only if main data source is already set. if (dataGrid.snapshot) this._baseProfile.load(dataGrid.setBaseDataSource.bind(dataGrid)); 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._filterSelect.selectedIndex() - 1; this._dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex); WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { action: WebInspector.UserMetrics.UserActionNames.HeapSnapshotFilterChanged, label: this._filterSelect.selectedOption().label }); 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); }, /** * @return {!Array.<!WebInspector.ProfileHeader>} */ _profiles: function() { return this._profile.profileType().getProfiles(); }, /** * @param {!WebInspector.ContextMenu} contextMenu * @param {!Event} event */ populateContextMenu: function(contextMenu, event) { if (this._dataGrid) this._dataGrid.populateContextMenu(contextMenu, event); }, _selectionChanged: function(event) { var selectedNode = event.target.selectedNode; this._setSelectedNodeForDetailsView(selectedNode); this._inspectedObjectChanged(event); }, _onSelectAllocationNode: function(event) { var selectedNode = event.target.selectedNode; this._constructorsDataGrid.setAllocationNodeId(selectedNode.allocationNodeId()); this._setSelectedNodeForDetailsView(null); }, _inspectedObjectChanged: function(event) { var selectedNode = event.target.selectedNode; var target = this._profile.target(); if (!target && selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode) target.consoleAgent().addInspectedHeapObject(selectedNode.snapshotNodeId); }, /** * @param {?WebInspector.HeapSnapshotGridNode} nodeItem */ _setSelectedNodeForDetailsView: function(nodeItem) { var dataSource = nodeItem && nodeItem.retainersDataSource(); if (dataSource) { this._retainmentDataGrid.setDataSource(dataSource.snapshot, dataSource.snapshotNodeIndex); if (this._allocationStackView) this._allocationStackView.setAllocatedObject(dataSource.snapshot, dataSource.snapshotNodeIndex) } else { if (this._allocationStackView) this._allocationStackView.clear(); this._retainmentDataGrid.reset(); } }, /** * @param {string} perspectiveTitle * @param {function()} callback */ _changePerspectiveAndWait: function(perspectiveTitle, callback) { var perspectiveIndex = null; for (var i = 0; i < this._perspectives.length; ++i) { if (this._perspectives[i].title() === perspectiveTitle) { perspectiveIndex = i; break; } } if (this._currentPerspectiveIndex === perspectiveIndex || perspectiveIndex === null) { setTimeout(callback, 0); return; } /** * @this {WebInspector.HeapSnapshotView} */ function dataGridContentShown(event) { var dataGrid = event.data; dataGrid.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this); if (dataGrid === this._dataGrid) callback(); } this._perspectives[perspectiveIndex].masterGrid(this).addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this); this._perspectiveSelect.setSelectedIndex(perspectiveIndex); this._changePerspective(perspectiveIndex); }, _updateDataSourceAndView: function() { var dataGrid = this._dataGrid; if (!dataGrid || dataGrid.snapshot) return; this._profile.load(didLoadSnapshot.bind(this)); /** * @this {WebInspector.HeapSnapshotView} */ function didLoadSnapshot(snapshotProxy) { if (this._dataGrid !== dataGrid) return; if (dataGrid.snapshot !== snapshotProxy) dataGrid.setDataSource(snapshotProxy); if (dataGrid === this._diffDataGrid) { if (!this._baseProfile) this._baseProfile = this._profiles()[this._baseSelect.selectedIndex()]; this._baseProfile.load(didLoadBaseSnaphot.bind(this)); } } /** * @this {WebInspector.HeapSnapshotView} */ function didLoadBaseSnaphot(baseSnapshotProxy) { if (this._diffDataGrid.baseSnapshot !== baseSnapshotProxy) this._diffDataGrid.setBaseDataSource(baseSnapshotProxy); } }, _onSelectedPerspectiveChanged: function(event) { this._changePerspective(event.target.selectedIndex); // FIXME: This is needed by CodeSchool extension. this._onSelectedViewChanged(event); }, _onSelectedViewChanged: function(event) { }, _changePerspective: function(selectedIndex) { if (selectedIndex === this._currentPerspectiveIndex) return; this._currentPerspectiveIndex = selectedIndex; this._currentPerspective.deactivate(this); var perspective = this._perspectives[selectedIndex]; this._currentPerspective = perspective; this._dataGrid = perspective.masterGrid(this); perspective.activate(this); this.refreshVisibleData(); if (this._dataGrid) this._dataGrid.updateWidths(); this._updateDataSourceAndView(); 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); }, /** * @param {string} perspectiveName * @param {number} snapshotObjectId */ highlightLiveObject: function(perspectiveName, snapshotObjectId) { this._changePerspectiveAndWait(perspectiveName, didChangePerspective.bind(this)); /** * @this {WebInspector.HeapSnapshotView} */ function didChangePerspective() { this._dataGrid.highlightObjectByHeapSnapshotId(snapshotObjectId, didHighlightObject); } function didHighlightObject(found) { if (!found) WebInspector.console.error("Cannot find corresponding heap snapshot node"); } }, _getHoverAnchor: function(target) { var span = target.enclosingNodeOrSelfWithNodeName("span"); if (!span) return; var row = target.enclosingNodeOrSelfWithNodeName("tr"); if (!row) return; span.node = row._dataGridNode; return span; }, _resolveObjectForPopover: function(element, showCallback, objectGroupName) { if (!this._profile.target()) return; element.node.queryObjectContent(this._profile.target(), showCallback, objectGroupName); }, _updateBaseOptions: function() { var list = this._profiles(); // We're assuming that snapshots can only be added. if (this._baseSelect.size() === list.length) return; for (var i = this._baseSelect.size(), n = list.length; i < n; ++i) { var title = list[i].title; this._baseSelect.createOption(title); } }, _updateFilterOptions: function() { var list = this._profiles(); // We're assuming that snapshots can only be added. if (this._filterSelect.size() - 1 === list.length) return; if (!this._filterSelect.size()) this._filterSelect.createOption(WebInspector.UIString("All objects")); for (var i = this._filterSelect.size() - 1, n = list.length; i < n; ++i) { var title = list[i].title; if (!i) title = WebInspector.UIString("Objects allocated before %s", title); else title = WebInspector.UIString("Objects allocated between %s and %s", list[i - 1].title, title); this._filterSelect.createOption(title); } }, _updateControls: function() { this._updateBaseOptions(); this._updateFilterOptions(); }, /** * @param {!WebInspector.Event} event */ _onReceiveSnapshot: function(event) { this._updateControls(); }, /** * @param {!WebInspector.Event} event */ _onProfileHeaderRemoved: function(event) { var profile = event.data; if (this._profile === profile) { this.detach(); this._profile.profileType().removeEventListener(WebInspector.HeapSnapshotProfileType.SnapshotReceived, this._onReceiveSnapshot, this); this._profile.profileType().removeEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this); this.dispose(); } else { this._updateControls(); } }, dispose: function() { if (this._allocationStackView) { this._allocationStackView.clear(); this._allocationDataGrid.dispose(); } }, __proto__: WebInspector.VBox.prototype } /** * @constructor * @extends {WebInspector.ProfileType} * @implements {WebInspector.TargetManager.Observer} * @param {string=} id * @param {string=} title */ WebInspector.HeapSnapshotProfileType = function(id, title) { WebInspector.ProfileType.call(this, id || WebInspector.HeapSnapshotProfileType.TypeId, title || WebInspector.UIString("Heap Snapshot")); WebInspector.targetManager.observeTargets(this); WebInspector.targetManager.addModelListener(WebInspector.HeapProfilerModel, WebInspector.HeapProfilerModel.Events.ResetProfiles, this._resetProfiles, this); WebInspector.targetManager.addModelListener(WebInspector.HeapProfilerModel, WebInspector.HeapProfilerModel.Events.AddHeapSnapshotChunk, this._addHeapSnapshotChunk, this); WebInspector.targetManager.addModelListener(WebInspector.HeapProfilerModel, WebInspector.HeapProfilerModel.Events.ReportHeapSnapshotProgress, this._reportHeapSnapshotProgress, this); } WebInspector.HeapSnapshotProfileType.TypeId = "HEAP"; WebInspector.HeapSnapshotProfileType.SnapshotReceived = "SnapshotReceived"; WebInspector.HeapSnapshotProfileType.prototype = { /** * @param {!WebInspector.Target} target */ targetAdded: function(target) { target.heapProfilerModel.enable(); }, /** * @param {!WebInspector.Target} target */ targetRemoved: function(target) { }, /** * @override * @return {string} */ fileExtension: function() { return ".heapsnapshot"; }, get buttonTooltip() { return WebInspector.UIString("Take heap snapshot."); }, /** * @override * @return {boolean} */ isInstantProfile: function() { return true; }, /** * @override * @return {boolean} */ buttonClicked: function() { this._takeHeapSnapshot(function() {}); WebInspector.userMetrics.ProfilesHeapProfileTaken.record(); return false; }, 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."); }, /** * @override * @param {string} title * @return {!WebInspector.ProfileHeader} */ createProfileLoadedFromFile: function(title) { return new WebInspector.HeapProfileHeader(null, this, title); }, _takeHeapSnapshot: function(callback) { if (this.profileBeingRecorded()) return; var target = /** @type {!WebInspector.Target} */ (WebInspector.context.flavor(WebInspector.Target)); var profile = new WebInspector.HeapProfileHeader(target, this); this.setProfileBeingRecorded(profile); this.addProfile(profile); profile.updateStatus(WebInspector.UIString("Snapshotting\u2026")); /** * @param {?string} error * @this {WebInspector.HeapSnapshotProfileType} */ function didTakeHeapSnapshot(error) { var profile = this._profileBeingRecorded; profile.title = WebInspector.UIString("Snapshot %d", profile.uid); profile._finishLoad(); this.setProfileBeingRecorded(null); this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, profile); callback(); } target.heapProfilerAgent().takeHeapSnapshot(true, didTakeHeapSnapshot.bind(this)); }, /** * @param {!WebInspector.Event} event */ _addHeapSnapshotChunk: function(event) { if (!this.profileBeingRecorded()) return; var chunk = /** @type {string} */(event.data); this.profileBeingRecorded().transferChunk(chunk); }, /** * @param {!WebInspector.Event} event */ _reportHeapSnapshotProgress: function(event) { var profile = this.profileBeingRecorded(); if (!profile) return; var data = /** @type {{done: number, total: number, finished: boolean}} */ (event.data); profile.updateStatus(WebInspector.UIString("%.0f%", (data.done / data.total) * 100), true); if (data.finished) profile._prepareToLoad(); }, _resetProfiles: function() { this._reset(); }, _snapshotReceived: function(profile) { if (this._profileBeingRecorded === profile) this.setProfileBeingRecorded(null); this.dispatchEventToListeners(WebInspector.HeapSnapshotProfileType.SnapshotReceived, profile); }, __proto__: WebInspector.ProfileType.prototype } /** * @constructor * @extends {WebInspector.HeapSnapshotProfileType} */ WebInspector.TrackingHeapSnapshotProfileType = function() { WebInspector.HeapSnapshotProfileType.call(this, WebInspector.TrackingHeapSnapshotProfileType.TypeId, WebInspector.UIString("Record Heap Allocations")); } WebInspector.TrackingHeapSnapshotProfileType.TypeId = "HEAP-RECORD"; WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate = "HeapStatsUpdate"; WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted = "TrackingStarted"; WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped = "TrackingStopped"; WebInspector.TrackingHeapSnapshotProfileType.prototype = { /** * @param {!WebInspector.Target} target */ targetAdded: function(target) { WebInspector.HeapSnapshotProfileType.prototype.targetAdded.call(this, target); target.heapProfilerModel.addEventListener(WebInspector.HeapProfilerModel.Events.HeapStatsUpdate, this._heapStatsUpdate, this); target.heapProfilerModel.addEventListener(WebInspector.HeapProfilerModel.Events.LastSeenObjectId, this._lastSeenObjectId, this); }, /** * @param {!WebInspector.Target} target */ targetRemoved: function(target) { WebInspector.HeapSnapshotProfileType.prototype.targetRemoved.call(this, target); target.heapProfilerModel.removeEventListener(WebInspector.HeapProfilerModel.Events.HeapStatsUpdate, this._heapStatsUpdate, this); target.heapProfilerModel.removeEventListener(WebInspector.HeapProfilerModel.Events.LastSeenObjectId, this._lastSeenObjectId, this); }, /** * @param {!WebInspector.Event} event */ _heapStatsUpdate: function(event) { if (!this._profileSamples) return; var samples = /** @type {!Array.<number>} */ (event.data); var index; for (var i = 0; i < samples.length; i += 3) { index = samples[i]; var count = samples[i+1]; var size = samples[i+2]; this._profileSamples.sizes[index] = size; if (!this._profileSamples.max[index]) this._profileSamples.max[index] = size; } }, /** * @param {!WebInspector.Event} event */ _lastSeenObjectId: function(event) { var profileSamples = this._profileSamples; if (!profileSamples) return; var data = /** @type {{lastSeenObjectId: number, timestamp: number}} */ (event.data); var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1); profileSamples.ids[currentIndex] = data.lastSeenObjectId; if (!profileSamples.max[currentIndex]) { profileSamples.max[currentIndex] = 0; profileSamples.sizes[currentIndex] = 0; } profileSamples.timestamps[currentIndex] = data.timestamp; if (profileSamples.totalTime < data.timestamp - profileSamples.timestamps[0]) profileSamples.totalTime *= 2; this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples); this._profileBeingRecorded.updateStatus(null, true); }, /** * @override * @return {boolean} */ hasTemporaryView: function() { return true; }, get buttonTooltip() { return this._recording ? WebInspector.UIString("Stop recording heap profile.") : WebInspector.UIString("Start recording heap profile."); }, /** * @override * @return {boolean} */ isInstantProfile: function() { return false; }, /** * @override * @return {boolean} */ buttonClicked: function() { return this._toggleRecording(); }, _startRecordingProfile: function() { if (this.profileBeingRecorded()) return; var recordAllocationStacks = WebInspector.settings.recordAllocationStacks.get(); this._addNewProfile(recordAllocationStacks); this.profileBeingRecorded().target().heapProfilerAgent().startTrackingHeapObjects(recordAllocationStacks); }, /** * @param {boolean} withAllocationStacks */ _addNewProfile: function(withAllocationStacks) { var target = WebInspector.context.flavor(WebInspector.Target); this.setProfileBeingRecorded(new WebInspector.HeapProfileHeader(target, this, undefined, withAllocationStacks)); this._lastSeenIndex = -1; this._profileSamples = { 'sizes': [], 'ids': [], 'timestamps': [], 'max': [], 'totalTime': 30000 }; this._profileBeingRecorded._profileSamples = this._profileSamples; this._recording = true; this.addProfile(this._profileBeingRecorded); this._profileBeingRecorded.updateStatus(WebInspector.UIString("Recording\u2026")); this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted); }, _stopRecordingProfile: function() { this._profileBeingRecorded.updateStatus(WebInspector.UIString("Snapshotting\u2026")); /** * @param {?string} error * @this {WebInspector.HeapSnapshotProfileType} */ function didTakeHeapSnapshot(error) { var profile = this.profileBeingRecorded(); if (!profile) return; profile._finishLoad(); this._profileSamples = null; this.setProfileBeingRecorded(null); this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, profile); } this._profileBeingRecorded.target().heapProfilerAgent().stopTrackingHeapObjects(true, didTakeHeapSnapshot.bind(this)); this._recording = false; this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped); }, _toggleRecording: function() { if (this._recording) this._stopRecordingProfile(); else this._startRecordingProfile(); return this._recording; }, get treeItemTitle() { return WebInspector.UIString("HEAP TIMELINES"); }, get description() { return WebInspector.UIString("Record JavaScript object allocations over time. Use this profile type to isolate memory leaks."); }, /** * @override */ resetProfiles: function() { var wasRecording = this._recording; var recordingAllocationStacks = wasRecording && this.profileBeingRecorded()._hasAllocationStacks; // Clear current profile to avoid stopping backend. this.setProfileBeingRecorded(null); WebInspector.HeapSnapshotProfileType.prototype.resetProfiles.call(this); this._profileSamples = null; this._lastSeenIndex = -1; if (wasRecording) this._addNewProfile(recordingAllocationStacks); }, /** * @override */ profileBeingRecordedRemoved: function() { this._stopRecordingProfile(); this._profileSamples = null; }, __proto__: WebInspector.HeapSnapshotProfileType.prototype } /** * @constructor * @extends {WebInspector.ProfileHeader} * @param {?WebInspector.Target} target * @param {!WebInspector.HeapSnapshotProfileType} type * @param {string=} title * @param {boolean=} hasAllocationStacks */ WebInspector.HeapProfileHeader = function(target, type, title, hasAllocationStacks) { WebInspector.ProfileHeader.call(this, target, type, title || WebInspector.UIString("Snapshot %d", type.nextProfileUid())); this._hasAllocationStacks = !!hasAllocationStacks; this.maxJSObjectId = -1; /** * @type {?WebInspector.HeapSnapshotWorkerProxy} */ this._workerProxy = null; /** * @type {?WebInspector.OutputStream} */ this._receiver = null; /** * @type {?WebInspector.HeapSnapshotProxy} */ this._snapshotProxy = null; /** * @type {?Array.<function(!WebInspector.HeapSnapshotProxy)>} */ this._loadCallbacks = []; this._totalNumberOfChunks = 0; this._bufferedWriter = null; } WebInspector.HeapProfileHeader.prototype = { /** * @override * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate * @return {!WebInspector.ProfileSidebarTreeElement} */ createSidebarTreeElement: function(dataDisplayDelegate) { return new WebInspector.ProfileSidebarTreeElement(dataDisplayDelegate, this, "heap-snapshot-sidebar-tree-item"); }, /** * @override * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate * @return {!WebInspector.HeapSnapshotView} */ createView: function(dataDisplayDelegate) { return new WebInspector.HeapSnapshotView(dataDisplayDelegate, this); }, /** * @override * @param {function(!WebInspector.HeapSnapshotProxy):void} callback */ load: function(callback) { if (this.uid === -1) return; if (this._snapshotProxy) { callback(this._snapshotProxy); return; } this._loadCallbacks.push(callback); }, _prepareToLoad: function() { console.assert(!this._receiver, "Already loading"); this._setupWorker(); this.updateStatus(WebInspector.UIString("Loading\u2026"), true); }, _finishLoad: function() { if (!this._wasDisposed) this._receiver.close(function() {}); if (this._bufferedWriter) { this._bufferedWriter.close(this._didWriteToTempFile