blob: 7768585b735eabad024e462d6c26d5c5177c6e92 [file] [log] [blame]
/*
* 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.View}
* @param {!WebInspector.ProfilesPanel} parent
* @param {!WebInspector.HeapProfileHeader} profile
*/
WebInspector.HeapSnapshotView = function(parent, profile)
{
WebInspector.View.call(this);
this.element.classList.add("heap-snapshot-view");
this.parent = parent;
this.parent.addEventListener("profile added", this._onProfileHeaderAdded, 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._trackingOverviewGrid.show(this.element);
}
this.viewsContainer = document.createElement("div");
this.viewsContainer.classList.add("views-container");
this.element.appendChild(this.viewsContainer);
this.containmentView = new WebInspector.View();
this.containmentView.element.classList.add("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.classList.add("view");
this.constructorsView.element.appendChild(this._createToolbarWithClassNameFilter());
this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid();
this.constructorsDataGrid.element.classList.add("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.dataGrid = /** @type {!WebInspector.HeapSnapshotSortableDataGrid} */ (this.constructorsDataGrid);
this.currentView = this.constructorsView;
this.currentView.show(this.viewsContainer);
this.diffView = new WebInspector.View();
this.diffView.element.classList.add("view");
this.diffView.element.appendChild(this._createToolbarWithClassNameFilter());
this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid();
this.diffDataGrid.element.classList.add("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.classList.add("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);
if (WebInspector.HeapSnapshot.enableAllocationProfiler) {
this.allocationView = new WebInspector.View();
this.allocationView.element.classList.add("view");
this.allocationDataGrid = new WebInspector.AllocationDataGrid();
this.allocationDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
this.allocationDataGrid.show(this.allocationView.element);
this.allocationDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
}
this.retainmentViewHeader = document.createElement("div");
this.retainmentViewHeader.classList.add("retainers-view-header");
WebInspector.installDragHandle(this.retainmentViewHeader, this._startRetainersHeaderDragging.bind(this), this._retainersHeaderDragging.bind(this), this._endRetainersHeaderDragging.bind(this), "row-resize");
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.classList.add("view");
this.retainmentView.element.classList.add("retaining-paths-view");
this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid();
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.viewSelect = new WebInspector.StatusBarComboBox(this._onSelectedViewChanged.bind(this));
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}];
if (WebInspector.settings.showAdvancedHeapSnapshotProperties.get())
this.views.push({title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid});
if (WebInspector.HeapSnapshot.enableAllocationProfiler)
this.views.push({title: "Allocation", view: this.allocationView, grid: this.allocationDataGrid});
this.views.current = 0;
for (var i = 0; i < this.views.length; ++i)
this.viewSelect.createOption(WebInspector.UIString(this.views[i].title));
this._profile = profile;
this.baseSelect = new WebInspector.StatusBarComboBox(this._changeBase.bind(this));
this.baseSelect.element.classList.add("hidden");
this._updateBaseOptions();
this.filterSelect = new WebInspector.StatusBarComboBox(this._changeFilter.bind(this));
this._updateFilterOptions();
this.selectedSizeText = new WebInspector.StatusBarText("");
this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true);
this._refreshView();
}
WebInspector.HeapSnapshotView.prototype = {
_refreshView: function()
{
this.profile.load(profileCallback.bind(this));
function profileCallback(heapSnapshotProxy)
{
var list = this._profiles();
var profileIndex = list.indexOf(this._profile);
this.baseSelect.setSelectedIndex(Math.max(0, profileIndex - 1));
this.dataGrid.setDataSource(heapSnapshotProxy);
}
},
_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);
},
dispose: function()
{
this.parent.removeEventListener("profile added", this._onProfileHeaderAdded, this);
this.profile.dispose();
if (this.baseProfile)
this.baseProfile.dispose();
this.containmentDataGrid.dispose();
this.constructorsDataGrid.dispose();
this.diffDataGrid.dispose();
this.dominatorDataGrid.dispose();
this.retainmentDataGrid.dispose();
},
get statusBarItems()
{
return [this.viewSelect.element, this.baseSelect.element, this.filterSelect.element, this.selectedSizeText.element];
},
get profile()
{
return this._profile;
},
get baseProfile()
{
return this._profile.profileType().getProfile(this._baseProfileUid);
},
wasShown: function()
{
// FIXME: load base and current snapshots in parallel
this.profile.load(profileCallback.bind(this));
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();
},
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 = [];
},
/**
* @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.currentView !== this.constructorsView && this.currentView !== this.diffView)
return;
/**
* @param {boolean} found
*/
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);
},
showingFirstSearchResult: function()
{
return (this._currentSearchResultIndex === 0);
},
showingLastSearchResult: function()
{
return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
},
currentSearchResultIndex: function() {
return this._currentSearchResultIndex;
},
_jumpToSearchResult: function(index)
{
var searchResult = this._searchResults[index];
if (!searchResult)
return;
var node = searchResult.node;
node.revealAndSelect();
},
refreshVisibleData: function()
{
var child = this.dataGrid.rootNode().children[0];
while (child) {
child.refresh();
child = child.traverseNextNode(false, null, true);
}
},
_changeBase: function()
{
if (this._baseProfileUid === this._profiles()[this.baseSelect.selectedIndex()].uid)
return;
this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid;
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);
},
_createToolbarWithClassNameFilter: function()
{
var toolbar = document.createElement("div");
toolbar.classList.add("class-view-toolbar");
var classNameFilter = document.createElement("input");
classNameFilter.classList.add("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;
this.dataGrid.changeNameFilter(filter);
},
/**
* @return {!Array.<!WebInspector.ProfileHeader>}
*/
_profiles: function()
{
return this._profile.profileType().getProfiles();
},
/**
* @param {!WebInspector.ContextMenu} contextMenu
* @param {?Event} event
*/
populateContextMenu: function(contextMenu, event)
{
this.dataGrid.populateContextMenu(this.parent, contextMenu, event);
},
_selectionChanged: function(event)
{
var selectedNode = event.target.selectedNode;
this._setRetainmentDataGridSource(selectedNode);
this._inspectedObjectChanged(event);
},
_inspectedObjectChanged: function(event)
{
var selectedNode = event.target.selectedNode;
if (!this.profile.fromFile() && selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode)
ConsoleAgent.addInspectedHeapObject(selectedNode.snapshotNodeId);
},
_setRetainmentDataGridSource: function(nodeItem)
{
if (nodeItem && nodeItem.snapshotNodeIndex)
this.retainmentDataGrid.setDataSource(nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex);
else
this.retainmentDataGrid.reset();
},
_mouseDownInContentsGrid: function(event)
{
if (event.detail < 2)
return;
var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
if (!cell || (!cell.classList.contains("count-column") && !cell.classList.contains("shallowSize-column") && !cell.classList.contains("retainedSize-column")))
return;
event.consume(true);
},
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 || viewIndex == null) {
setTimeout(callback, 0);
return;
}
function dataGridContentShown(event)
{
var dataGrid = event.data;
dataGrid.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
if (dataGrid === this.dataGrid)
callback();
}
this.views[viewIndex].grid.addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
this.viewSelect.setSelectedIndex(viewIndex);
this._changeView(viewIndex);
},
_updateDataSourceAndView: function()
{
var dataGrid = this.dataGrid;
if (dataGrid.snapshot)
return;
this.profile.load(didLoadSnapshot.bind(this));
function didLoadSnapshot(snapshotProxy)
{
if (this.dataGrid !== dataGrid)
return;
if (dataGrid.snapshot !== snapshotProxy)
dataGrid.setDataSource(snapshotProxy);
if (dataGrid === this.diffDataGrid) {
if (!this._baseProfileUid)
this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid;
this.baseProfile.load(didLoadBaseSnaphot.bind(this));
}
}
function didLoadBaseSnaphot(baseSnapshotProxy)
{
if (this.diffDataGrid.baseSnapshot !== baseSnapshotProxy)
this.diffDataGrid.setBaseDataSource(baseSnapshotProxy);
}
},
_onSelectedViewChanged: function(event)
{
this._changeView(event.target.selectedIndex);
},
_updateSelectorsVisibility: function()
{
if (this.currentView === this.diffView)
this.baseSelect.element.classList.remove("hidden");
else
this.baseSelect.element.classList.add("hidden");
if (this.currentView === this.constructorsView) {
if (this._trackingOverviewGrid) {
this._trackingOverviewGrid.element.classList.remove("hidden");
this._trackingOverviewGrid.update();
this.viewsContainer.classList.add("reserve-80px-at-top");
}
this.filterSelect.element.classList.remove("hidden");
} else {
this.filterSelect.element.classList.add("hidden");
if (this._trackingOverviewGrid) {
this._trackingOverviewGrid.element.classList.add("hidden");
this.viewsContainer.classList.remove("reserve-80px-at-top");
}
}
},
_changeView: function(selectedIndex)
{
if (selectedIndex === this.views.current)
return;
this.views.current = 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();
this._updateSelectorsVisibility();
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);
},
_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.fromFile())
return;
element.node.queryObjectContent(showCallback, objectGroupName);
},
/**
* @return {boolean}
*/
_startRetainersHeaderDragging: function(event)
{
if (!this.isShowing())
return false;
this._previousDragPosition = event.pageY;
return true;
},
_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)
{
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";
if (this._trackingOverviewGrid && this.currentView === this.constructorsView)
this.viewsContainer.classList.add("reserve-80px-at-top");
this.retainmentView.element.style.height = height + "px";
this.retainmentViewHeader.style.bottom = height + "px";
this.currentView.doResize();
},
_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);
}
},
/**
* @param {!WebInspector.Event} event
*/
_onProfileHeaderAdded: function(event)
{
if (!event.data || event.data.type !== this._profile.profileType().id)
return;
this._updateBaseOptions();
this._updateFilterOptions();
},
__proto__: WebInspector.View.prototype
}
/**
* @constructor
* @implements {HeapProfilerAgent.Dispatcher}
*/
WebInspector.HeapProfilerDispatcher = function()
{
this._dispatchers = [];
InspectorBackend.registerHeapProfilerDispatcher(this);
}
WebInspector.HeapProfilerDispatcher.prototype = {
/**
* @param {!HeapProfilerAgent.Dispatcher} dispatcher
*/
register: function(dispatcher)
{
this._dispatchers.push(dispatcher);
},
_genericCaller: function(eventName)
{
var args = Array.prototype.slice.call(arguments.callee.caller.arguments);
for (var i = 0; i < this._dispatchers.length; ++i)
this._dispatchers[i][eventName].apply(this._dispatchers[i], args);
},
/**
* @override
* @param {!Array.<number>} samples
*/
heapStatsUpdate: function(samples)
{
this._genericCaller("heapStatsUpdate");
},
/**
* @override
* @param {number} lastSeenObjectId
* @param {number} timestamp
*/
lastSeenObjectId: function(lastSeenObjectId, timestamp)
{
this._genericCaller("lastSeenObjectId");
},
/**
* @param {!HeapProfilerAgent.ProfileHeader} profileHeader
*/
addProfileHeader: function(profileHeader)
{
this._genericCaller("addProfileHeader");
},
/**
* @override
* @param {number} uid
* @param {string} chunk
*/
addHeapSnapshotChunk: function(uid, chunk)
{
this._genericCaller("addHeapSnapshotChunk");
},
/**
* @override
* @param {number} done
* @param {number} total
*/
reportHeapSnapshotProgress: function(done, total)
{
this._genericCaller("reportHeapSnapshotProgress");
},
/**
* @override
*/
resetProfiles: function()
{
this._genericCaller("resetProfiles");
}
}
WebInspector.HeapProfilerDispatcher._dispatcher = new WebInspector.HeapProfilerDispatcher();
/**
* @constructor
* @extends {WebInspector.ProfileType}
* @implements {HeapProfilerAgent.Dispatcher}
*/
WebInspector.HeapSnapshotProfileType = function()
{
WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("Take Heap Snapshot"));
WebInspector.HeapProfilerDispatcher._dispatcher.register(this);
}
WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
WebInspector.HeapSnapshotProfileType.SnapshotReceived = "SnapshotReceived";
WebInspector.HeapSnapshotProfileType.prototype = {
/**
* @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;
},
/**
* @override
* @param {!Array.<number>} samples
*/
heapStatsUpdate: function(samples)
{
},
/**
* @override
* @param {number} lastSeenObjectId
* @param {number} timestamp
*/
lastSeenObjectId: function(lastSeenObjectId, timestamp)
{
},
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(this, title);
},
_takeHeapSnapshot: function(callback)
{
if (this.profileBeingRecorded())
return;
this._profileBeingRecorded = new WebInspector.HeapProfileHeader(this, WebInspector.UIString("Snapshotting\u2026"));
this.addProfile(this._profileBeingRecorded);
HeapProfilerAgent.takeHeapSnapshot(true, callback);
},
/**
* @param {!HeapProfilerAgent.ProfileHeader} profileHeader
*/
addProfileHeader: function(profileHeader)
{
var profile = this.profileBeingRecorded();
if (!profile)
return;
profile.title = profileHeader.title;
profile.uid = profileHeader.uid;
profile.maxJSObjectId = profileHeader.maxJSObjectId || 0;
profile.sidebarElement.mainTitle = profile.title;
profile.sidebarElement.subtitle = "";
profile.sidebarElement.wait = false;
this._profileSamples = null;
this._profileBeingRecorded = null;
WebInspector.panels.profiles._showProfile(profile);
profile.existingView()._refreshView();
},
/**
* @override
* @param {number} uid
* @param {string} chunk
*/
addHeapSnapshotChunk: function(uid, chunk)
{
var profile = this.getProfile(uid);
if (profile)
profile.transferChunk(chunk);
},
/**
* @override
* @param {number} done
* @param {number} total
*/
reportHeapSnapshotProgress: function(done, total)
{
var profile = this.profileBeingRecorded();
if (!profile)
return;
profile.sidebarElement.subtitle = WebInspector.UIString("%.0f%", (done / total) * 100);
profile.sidebarElement.wait = true;
},
/**
* @override
*/
resetProfiles: function()
{
this._reset();
},
/**
* @override
* @param {!WebInspector.ProfileHeader} profile
*/
removeProfile: function(profile)
{
if (this._profileBeingRecorded !== profile && !profile.fromFile())
HeapProfilerAgent.removeProfile(profile.uid);
WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
},
_snapshotReceived: function(profile)
{
if (this._profileBeingRecorded === profile)
this._profileBeingRecorded = null;
this.dispatchEventToListeners(WebInspector.HeapSnapshotProfileType.SnapshotReceived, profile);
},
__proto__: WebInspector.ProfileType.prototype
}
/**
* @constructor
* @extends {WebInspector.HeapSnapshotProfileType}
* @param {!WebInspector.ProfilesPanel} profilesPanel
*/
WebInspector.TrackingHeapSnapshotProfileType = function(profilesPanel)
{
WebInspector.ProfileType.call(this, WebInspector.TrackingHeapSnapshotProfileType.TypeId, WebInspector.UIString("Record Heap Allocations"));
this._profilesPanel = profilesPanel;
WebInspector.HeapProfilerDispatcher._dispatcher.register(this);
}
WebInspector.TrackingHeapSnapshotProfileType.TypeId = "HEAP-RECORD";
WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate = "HeapStatsUpdate";
WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted = "TrackingStarted";
WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped = "TrackingStopped";
WebInspector.TrackingHeapSnapshotProfileType.prototype = {
/**
* @override
* @param {!Array.<number>} samples
*/
heapStatsUpdate: function(samples)
{
if (!this._profileSamples)
return;
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] || size > this._profileSamples.max[index])
this._profileSamples.max[index] = size;
}
this._lastUpdatedIndex = index;
},
/**
* @override
* @param {number} lastSeenObjectId
* @param {number} timestamp
*/
lastSeenObjectId: function(lastSeenObjectId, timestamp)
{
var profileSamples = this._profileSamples;
if (!profileSamples)
return;
var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1);
profileSamples.ids[currentIndex] = lastSeenObjectId;
if (!profileSamples.max[currentIndex]) {
profileSamples.max[currentIndex] = 0;
profileSamples.sizes[currentIndex] = 0;
}
profileSamples.timestamps[currentIndex] = timestamp;
if (profileSamples.totalTime < timestamp - profileSamples.timestamps[0])
profileSamples.totalTime *= 2;
this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples);
var profile = this._profileBeingRecorded;
profile.sidebarElement.wait = true;
if (profile.sidebarElement && !profile.sidebarElement.wait)
profile.sidebarElement.wait = 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;
this._profileBeingRecorded = new WebInspector.HeapProfileHeader(this, WebInspector.UIString("Recording\u2026"));
this._lastSeenIndex = -1;
this._profileSamples = {
'sizes': [],
'ids': [],
'timestamps': [],
'max': [],
'totalTime': 30000
};
this._profileBeingRecorded._profileSamples = this._profileSamples;
this._recording = true;
this.addProfile(this._profileBeingRecorded);
HeapProfilerAgent.startTrackingHeapObjects();
this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted);
},
_stopRecordingProfile: function()
{
HeapProfilerAgent.stopTrackingHeapObjects(true);
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.");
},
_reset: function()
{
WebInspector.HeapSnapshotProfileType.prototype._reset.call(this);
if (this._recording)
this._stopRecordingProfile();
this._profileSamples = null;
this._lastSeenIndex = -1;
},
__proto__: WebInspector.HeapSnapshotProfileType.prototype
}
/**
* @constructor
* @extends {WebInspector.ProfileHeader}
* @param {!WebInspector.ProfileType} type
* @param {string} title
* @param {number=} uid
* @param {number=} maxJSObjectId
*/
WebInspector.HeapProfileHeader = function(type, title, uid, maxJSObjectId)
{
WebInspector.ProfileHeader.call(this, type, title, uid);
this.maxJSObjectId = maxJSObjectId;
/**
* @type {?WebInspector.OutputStream}
*/
this._receiver = null;
/**
* @type {?WebInspector.HeapSnapshotProxy}
*/
this._snapshotProxy = null;
this._totalNumberOfChunks = 0;
this._transferHandler = null;
}
WebInspector.HeapProfileHeader.prototype = {
/**
* @override
*/
createSidebarTreeElement: function()
{
return new WebInspector.ProfileSidebarTreeElement(this, "heap-snapshot-sidebar-tree-item");
},
/**
* @override
* @param {!WebInspector.ProfilesPanel} profilesPanel
*/
createView: function(profilesPanel)
{
return new WebInspector.HeapSnapshotView(profilesPanel, this);
},
/**
* @override
* @param {function(!WebInspector.HeapSnapshotProxy):void} callback
*/
load: function(callback)
{
if (this.uid === -1)
return;
if (this._snapshotProxy) {
callback(this._snapshotProxy);
return;
}
this._numberOfChunks = 0;
if (!this._receiver) {
this._setupWorker();
this._transferHandler = new WebInspector.BackendSnapshotLoader(this);
this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
this.sidebarElement.wait = true;
this._transferSnapshot();
}
var loaderProxy = /** @type {?WebInspector.HeapSnapshotLoaderProxy} */ (this._receiver);
console.assert(loaderProxy);
loaderProxy.addConsumer(callback);
},
_transferSnapshot: function()
{
function finishTransfer()
{
if (this._transferHandler) {
this._transferHandler.finishTransfer();
this._totalNumberOfChunks = this._transferHandler._totalNumberOfChunks;
}
}
HeapProfilerAgent.getHeapSnapshot(this.uid, finishTransfer.bind(this));
},
snapshotConstructorName: function()
{
return "JSHeapSnapshot";
},
snapshotProxyConstructor: function()
{
return WebInspector.HeapSnapshotProxy;
},
_setupWorker: function()
{
function setProfileWait(event)
{
this.sidebarElement.wait = event.data;
}
var worker = new WebInspector.HeapSnapshotWorkerProxy(this._handleWorkerEvent.bind(this));
worker.addEventListener("wait", setProfileWait, this);
var loaderProxy = worker.createLoader(this.snapshotConstructorName(), this.snapshotProxyConstructor());
loaderProxy.addConsumer(this._snapshotReceived.bind(this));
this._receiver = loaderProxy;
},
/**
* @param {string} eventName
* @param {*} data
*/
_handleWorkerEvent: function(eventName, data)
{
if (WebInspector.HeapSnapshotProgress.Event.Update !== eventName)
return;
this._updateSubtitle(data);
},
/**
* @override
*/
dispose: function()
{
if (this._receiver)
this._receiver.close();
else if (this._snapshotProxy)
this._snapshotProxy.dispose();
if (this._view) {
var view = this._view;
this._view = null;
view.dispose();
}
},
_updateSubtitle: function(value)
{
this.sidebarElement.subtitle = value;
},
_didCompleteSnapshotTransfer: function()
{
this.sidebarElement.subtitle = Number.bytesToString(this._snapshotProxy.totalSize);
this.sidebarElement.wait = false;
},
/**
* @param {string} chunk
*/
transferChunk: function(chunk)
{
this._transferHandler.transferChunk(chunk);
},
_snapshotReceived: function(snapshotProxy)
{
this._receiver = null;
if (snapshotProxy)
this._snapshotProxy = snapshotProxy;
this._didCompleteSnapshotTransfer();
var worker = /** @type {!WebInspector.HeapSnapshotWorkerProxy} */ (this._snapshotProxy.worker);
worker.startCheckingForLongRunningCalls();
this.notifySnapshotReceived();
function didGetMaxNodeId(id)
{
this.maxJSObjectId = id;
}
if (this.fromFile())
snapshotProxy.maxJsNodeId(didGetMaxNodeId.bind(this));
},
notifySnapshotReceived: function()
{
this._profileType._snapshotReceived(this);
},
// Hook point for tests.
_wasShown: function()
{
},
/**
* @override
* @return {boolean}
*/
canSaveToFile: function()
{
return !this.fromFile() && !!this._snapshotProxy && !this._receiver;
},
/**
* @override
*/
saveToFile: function()
{
var fileOutputStream = new WebInspector.FileOutputStream();
/**
* @param {boolean} accepted
*/
function onOpen(accepted)
{
if (!accepted)
return;
this._receiver = fileOutputStream;
this._transferHandler = new WebInspector.SaveSnapshotHandler(this);
this._transferSnapshot();
}
this._fileName = this._fileName || "Heap-" + new Date().toISO8601Compact() + this._profileType.fileExtension();
fileOutputStream.open(this._fileName, onOpen.bind(this));
},
/**
* @override
* @param {!File} file
*/
loadFromFile: function(file)
{
this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
this.sidebarElement.wait = true;
this._setupWorker();
var delegate = new WebInspector.HeapSnapshotLoadFromFileDelegate(this);
var fileReader = this._createFileReader(file, delegate);
fileReader.start(this._receiver);
},
_createFileReader: function(file, delegate)
{
return new WebInspector.ChunkedFileReader(file, 10000000, delegate);
},
__proto__: WebInspector.ProfileHeader.prototype
}
/**
* @constructor
* @param {!WebInspector.HeapProfileHeader} header
* @param {string} title
*/
WebInspector.SnapshotTransferHandler = function(header, title)
{
this._numberOfChunks = 0;
this._savedChunks = 0;
this._header = header;
this._totalNumberOfChunks = 0;
this._title = title;
}
WebInspector.SnapshotTransferHandler.prototype = {
/**
* @param {string} chunk
*/
transferChunk: function(chunk)
{
++this._numberOfChunks;
this._header._receiver.write(chunk, this._didTransferChunk.bind(this));
},
finishTransfer: function()
{
},
_didTransferChunk: function()
{
this._updateProgress(++this._savedChunks, this._totalNumberOfChunks);
},
_updateProgress: function(value, total)
{
}
}
/**
* @constructor
* @param {!WebInspector.HeapProfileHeader} header
* @extends {WebInspector.SnapshotTransferHandler}
*/
WebInspector.SaveSnapshotHandler = function(header)
{
WebInspector.SnapshotTransferHandler.call(this, header, "Saving\u2026 %d\%");
this._totalNumberOfChunks = header._totalNumberOfChunks;
this._updateProgress(0, this._totalNumberOfChunks);
}
WebInspector.SaveSnapshotHandler.prototype = {
_updateProgress: function(value, total)
{
var percentValue = ((total ? (value / total) : 0) * 100).toFixed(0);
this._header._updateSubtitle(WebInspector.UIString(this._title, percentValue));
if (value === total) {
this._header._receiver.close();
this._header._didCompleteSnapshotTransfer();
}
},
__proto__: WebInspector.SnapshotTransferHandler.prototype
}
/**
* @constructor
* @param {!WebInspector.HeapProfileHeader} header
* @extends {WebInspector.SnapshotTransferHandler}
*/
WebInspector.BackendSnapshotLoader = function(header)
{
WebInspector.SnapshotTransferHandler.call(this, header, "Loading\u2026 %d\%");
}
WebInspector.BackendSnapshotLoader.prototype = {
finishTransfer: function()
{
this._header._receiver.close(this._didFinishTransfer.bind(this));
this._totalNumberOfChunks = this._numberOfChunks;
},
_didFinishTransfer: function()
{
console.assert(this._totalNumberOfChunks === this._savedChunks, "Not all chunks were transfered.");
},
__proto__: WebInspector.SnapshotTransferHandler.prototype
}
/**
* @constructor
* @implements {WebInspector.OutputStreamDelegate}
*/
WebInspector.HeapSnapshotLoadFromFileDelegate = function(snapshotHeader)
{
this._snapshotHeader = snapshotHeader;
}
WebInspector.HeapSnapshotLoadFromFileDelegate.prototype = {
onTransferStarted: function()
{
},
/**
* @param {!WebInspector.ChunkedReader} reader
*/
onChunkTransferred: function(reader)
{
},
onTransferFinished: function()
{
},
/**
* @param {!WebInspector.ChunkedReader} reader
*/
onError: function (reader, e)
{
switch(e.target.error.code) {
case e.target.error.NOT_FOUND_ERR:
this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' not found.", reader.fileName()));
break;
case e.target.error.NOT_READABLE_ERR:
this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' is not readable", reader.fileName()));
break;
case e.target.error.ABORT_ERR:
break;
default:
this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code));
}
}
}
/**
* @constructor
* @extends {WebInspector.View}
* @param {!WebInspector.HeapProfileHeader} heapProfileHeader
*/
WebInspector.HeapTrackingOverviewGrid = function(heapProfileHeader)
{
WebInspector.View.call(this);
this.registerRequiredCSS("flameChart.css");
this.element.id = "heap-recording-view";
this._overviewContainer = this.element.createChild("div", "overview-container");
this._overviewGrid = new WebInspector.OverviewGrid("heap-recording");
this._overviewGrid.element.classList.add("fill");
this._overviewCanvas = this._overviewContainer.createChild("canvas", "heap-recording-overview-canvas");
this._overviewContainer.appendChild(this._overviewGrid.element);
this._overviewCalculator = new WebInspector.HeapTrackingOverviewGrid.OverviewCalculator();
this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
this._profileSamples = heapProfileHeader._profileSamples;
if (heapProfileHeader.profileType().profileBeingRecorded() === heapProfileHeader) {
this._profileType = heapProfileHeader._profileType;
this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
}
var timestamps = this._profileSamples.timestamps;
var totalTime = this._profileSamples.totalTime;
this._windowLeft = 0.0;
this._windowRight = totalTime && timestamps.length ? (timestamps[timestamps.length - 1] - timestamps[0]) / totalTime : 1.0;
this._overviewGrid.setWindow(this._windowLeft, this._windowRight);
this._yScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
this._xScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
}
WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged = "IdsRangeChanged";
WebInspector.HeapTrackingOverviewGrid.prototype = {
_onStopTracking: function(event)
{
this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
},
_onHeapStatsUpdate: function(event)
{
this._profileSamples = event.data;
this._scheduleUpdate();
},
/**
* @param {number} width
* @param {number} height
*/
_drawOverviewCanvas: function(width, height)
{
if (!this._profileSamples)
return;
var profileSamples = this._profileSamples;
var sizes = profileSamples.sizes;
var topSizes = profileSamples.max;
var timestamps = profileSamples.timestamps;
var startTime = timestamps[0];
var endTime = timestamps[timestamps.length - 1];
var scaleFactor = this._xScale.nextScale(width / profileSamples.totalTime);
var maxSize = 0;
/**
* @param {!Array.<number>} sizes
* @param {function(number, number):void} callback
*/
function aggregateAndCall(sizes, callback)
{
var size = 0;
var currentX = 0;
for (var i = 1; i < timestamps.length; ++i) {
var x = Math.floor((timestamps[i] - startTime) * scaleFactor);
if (x !== currentX) {
if (size)
callback(currentX, size);
size = 0;
currentX = x;
}
size += sizes[i];
}
callback(currentX, size);
}
/**
* @param {number} x
* @param {number} size
*/
function maxSizeCallback(x, size)
{
maxSize = Math.max(maxSize, size);
}
aggregateAndCall(sizes, maxSizeCallback);
var yScaleFactor = this._yScale.nextScale(maxSize ? height / (maxSize * 1.1) : 0.0);
this._overviewCanvas.width = width * window.devicePixelRatio;
this._overviewCanvas.height = height * window.devicePixelRatio;
this._overviewCanvas.style.width = width + "px";
this._overviewCanvas.style.height = height + "px";
var context = this._overviewCanvas.getContext("2d");
context.scale(window.devicePixelRatio, window.devicePixelRatio);
context.beginPath();
context.lineWidth = 2;
context.strokeStyle = "rgba(192, 192, 192, 0.6)";
var currentX = (endTime - startTime) * scaleFactor;
context.moveTo(currentX, height - 1);
context.lineTo(currentX, 0);
context.stroke();
context.closePath();
var gridY;
var gridValue;
var gridLabelHeight = 14;
if (yScaleFactor) {
const maxGridValue = (height - gridLabelHeight) / yScaleFactor;
// The round value calculation is a bit tricky, because
// it has a form k*10^n*1024^m, where k=[1,5], n=[0..3], m is an integer,
// e.g. a round value 10KB is 10240 bytes.
gridValue = Math.pow(1024, Math.floor(Math.log(maxGridValue) / Math.log(1024)));
gridValue *= Math.pow(10, Math.floor(Math.log(maxGridValue / gridValue) / Math.LN10));
if (gridValue * 5 <= maxGridValue)
gridValue *= 5;
gridY = Math.round(height - gridValue * yScaleFactor - 0.5) + 0.5;
context.beginPath();
context.lineWidth = 1;
context.strokeStyle = "rgba(0, 0, 0, 0.2)";
context.moveTo(0, gridY);
context.lineTo(width, gridY);
context.stroke();
context.closePath();
}
/**
* @param {number} x
* @param {number} size
*/
function drawBarCallback(x, size)
{
context.moveTo(x, height - 1);
context.lineTo(x, Math.round(height - size * yScaleFactor - 1));
}
context.beginPath();
context.lineWidth = 2;
context.strokeStyle = "rgba(192, 192, 192, 0.6)";
aggregateAndCall(topSizes, drawBarCallback);
context.stroke();
context.closePath();
context.beginPath();
context.lineWidth = 2;
context.strokeStyle = "rgba(0, 0, 192, 0.8)";
aggregateAndCall(sizes, drawBarCallback);
context.stroke();
context.closePath();
if (gridValue) {
var label = Number.bytesToString(gridValue);
var labelPadding = 4;
var labelX = 0;
var labelY = gridY - 0.5;
var labelWidth = 2 * labelPadding + context.measureText(label).width;
context.beginPath();
context.textBaseline = "bottom";
context.font = "10px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
context.fillStyle = "rgba(255, 255, 255, 0.75)";
context.fillRect(labelX, labelY - gridLabelHeight, labelWidth, gridLabelHeight);
context.fillStyle = "rgb(64, 64, 64)";
context.fillText(label, labelX + labelPadding, labelY);
context.fill();
context.closePath();
}
},
onResize: function()
{
this._updateOverviewCanvas = true;
this._scheduleUpdate();
},
_onWindowChanged: function()
{
if (!this._updateGridTimerId)
this._updateGridTimerId = setTimeout(this._updateGrid.bind(this), 10);
},
_scheduleUpdate: function()
{
if (this._updateTimerId)
return;
this._updateTimerId = setTimeout(this.update.bind(this), 10);
},
_updateBoundaries: function()
{
this._windowLeft = this._overviewGrid.windowLeft();
this._windowRight = this._overviewGrid.windowRight();
this._windowWidth = this._windowRight - this._windowLeft;
},
update: function()
{
this._updateTimerId = null;
if (!this.isShowing())
return;
this._updateBoundaries();
this._overviewCalculator._updateBoundaries(this);
this._overviewGrid.updateDividers(this._overviewCalculator);
this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20);
},
_updateGrid: function()
{
this._updateGridTimerId = 0;
this._updateBoundaries();
var ids = this._profileSamples.ids;
var timestamps = this._profileSamples.timestamps;
var sizes = this._profileSamples.sizes;
var startTime = timestamps[0];
var totalTime = this._profileSamples.totalTime;
var timeLeft = startTime + totalTime * this._windowLeft;
var timeRight = startTime + totalTime * this._windowRight;
var minId = 0;
var maxId = ids[ids.length - 1] + 1;
var size = 0;
for (var i = 0; i < timestamps.length; ++i) {
if (!timestamps[i])
continue;
if (timestamps[i] > timeRight)
break;
maxId = ids[i];
if (timestamps[i] < timeLeft) {
minId = ids[i];
continue;
}
size += sizes[i];
}
this.dispatchEventToListeners(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, {minId: minId, maxId: maxId, size: size});
},
__proto__: WebInspector.View.prototype
}
/**
* @constructor
*/
WebInspector.HeapTrackingOverviewGrid.SmoothScale = function()
{
this._lastUpdate = 0;
this._currentScale = 0.0;
}
WebInspector.HeapTrackingOverviewGrid.SmoothScale.prototype = {
/**
* @param {number} target
* @return {number}
*/
nextScale: function(target) {
target = target || this._currentScale;
if (this._currentScale) {
var now = Date.now();
var timeDeltaMs = now - this._lastUpdate;
this._lastUpdate = now;
var maxChangePerSec = 20;
var maxChangePerDelta = Math.pow(maxChangePerSec, timeDeltaMs / 1000);
var scaleChange = target / this._currentScale;
this._currentScale *= Number.constrain(scaleChange, 1 / maxChangePerDelta, maxChangePerDelta);
} else
this._currentScale = target;
return this._currentScale;
}
}
/**
* @constructor
* @implements {WebInspector.TimelineGrid.Calculator}
*/
WebInspector.HeapTrackingOverviewGrid.OverviewCalculator = function()
{
}
WebInspector.HeapTrackingOverviewGrid.OverviewCalculator.prototype = {
/**
* @param {!WebInspector.HeapTrackingOverviewGrid} chart
*/
_updateBoundaries: function(chart)
{
this._minimumBoundaries = 0;
this._maximumBoundaries = chart._profileSamples.totalTime;
this._xScaleFactor = chart._overviewContainer.clientWidth / this._maximumBoundaries;
},
/**
* @param {number} time
*/
computePosition: function(time)
{
return (time - this._minimumBoundaries) * this._xScaleFactor;
},
/**
* @param {number} value
* @param {boolean=} hires
* @return {string}
*/
formatTime: function(value, hires)
{
return Number.secondsToString((value + this._minimumBoundaries) / 1000, hires);
},
maximumBoundary: function()
{
return this._maximumBoundaries;
},
minimumBoundary: function()
{
return this._minimumBoundaries;
},
zeroTime: function()
{
return this._minimumBoundaries;
},
boundarySpan: function()
{
return this._maximumBoundaries - this._minimumBoundaries;
}
}