strong-arc
Version:
A visual suite for the StrongLoop API Platform
1,451 lines (1,264 loc) • 73 kB
JavaScript
/*
* 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