blob: 61349ad0691c198222e28711a77713785cba971c [file] [log] [blame]
const UserInitiatedProfileName = "org.webkit.profiles.user-initiated";
WebInspector.ProfileType = function(id, name)
{
this._id = id;
this._name = name;
this._profiles = [];
this._profilesIdMap = {};
this.treeElement = null;
}
WebInspector.ProfileType.Events = {
AddProfileHeader: "add-profile-header",
RemoveProfileHeader: "remove-profile-header",
ProgressUpdated: "progress-updated",
ViewUpdated: "view-updated"
}
WebInspector.ProfileType.prototype = {
fileExtension: function()
{
return null;
},
get statusBarItems()
{
return [];
},
get buttonTooltip()
{
return "";
},
get id()
{
return this._id;
},
get treeItemTitle()
{
return this._name;
},
get name()
{
return this._name;
},
buttonClicked: function()
{
return false;
},
get description()
{
return "";
},
isInstantProfile: function()
{
return false;
},
getProfiles: function()
{
return this._profiles.filter(function(profile) { return !profile.isTemporary; });
},
decorationElement: function()
{
return null;
},
getProfile: function(uid)
{
return this._profilesIdMap[this._makeKey(uid)];
},
createTemporaryProfile: function(title)
{
throw new Error("Needs implemented.");
},
createProfile: function(profile)
{
throw new Error("Not supported for " + this._name + " profiles.");
},
_makeKey: function(id)
{
return id + '/' + escape(this.id);
},
addProfile: function(profile)
{
this._profiles.push(profile);
this._profilesIdMap[this._makeKey(profile.uid)] = profile;
this.dispatchEventToListeners(WebInspector.ProfileType.Events.AddProfileHeader, profile);
},
removeProfile: function(profile)
{
for (var i = 0; i < this._profiles.length; ++i) {
if (this._profiles[i].uid === profile.uid) {
this._profiles.splice(i, 1);
break;
}
}
delete this._profilesIdMap[this._makeKey(profile.uid)];
},
findTemporaryProfile: function()
{
for (var i = 0; i < this._profiles.length; ++i) {
if (this._profiles[i].isTemporary)
return this._profiles[i];
}
return null;
},
_reset: function()
{
var profiles = this._profiles.slice(0);
for (var i = 0; i < profiles.length; ++i) {
var profile = profiles[i];
var view = profile.existingView();
if (view) {
view.detach();
if ("dispose" in view)
view.dispose();
}
this.dispatchEventToListeners(WebInspector.ProfileType.Events.RemoveProfileHeader, profile);
}
this.treeElement.removeChildren();
this._profiles = [];
this._profilesIdMap = {};
},
_requestProfilesFromBackend: function(populateCallback)
{
},
_populateProfiles: function()
{
function populateCallback(error, profileHeaders) {
if (error)
return;
profileHeaders.sort(function(a, b) { return a.uid - b.uid; });
var count = profileHeaders.length;
for (var i = 0; i < count; ++i)
this.addProfile(this.createProfile(profileHeaders[i]));
}
this._requestProfilesFromBackend(populateCallback.bind(this));
},
__proto__: WebInspector.Object.prototype
}
WebInspector.ProfileHeader = function(profileType, title, uid)
{
this._profileType = profileType;
this.title = title;
this.isTemporary = uid === undefined;
this.uid = this.isTemporary ? -1 : uid;
this._fromFile = false;
}
WebInspector.ProfileHeader.prototype = {
profileType: function()
{
return this._profileType;
},
createSidebarTreeElement: function()
{
throw new Error("Needs implemented.");
},
existingView: function()
{
return this._view;
},
view: function(panel)
{
if (!this._view)
this._view = this.createView(panel);
return this._view;
},
createView: function(panel)
{
throw new Error("Not implemented.");
},
dispose: function()
{
},
load: function(callback)
{
},
canSaveToFile: function()
{
return false;
},
saveToFile: function()
{
throw new Error("Needs implemented");
},
loadFromFile: function(file)
{
throw new Error("Needs implemented");
},
fromFile: function()
{
return this._fromFile;
}
}
WebInspector.ProfilesPanel = function(name, type)
{
var singleProfileMode = typeof name !== "undefined";
name = name || "profiles";
WebInspector.Panel.call(this, name);
this.registerRequiredCSS("panelEnablerView.css");
this.registerRequiredCSS("heapProfiler.css");
this.registerRequiredCSS("profilesPanel.css");
this.createSidebarViewWithTree();
this.profilesItemTreeElement = new WebInspector.ProfilesSidebarTreeElement(this);
this.sidebarTree.appendChild(this.profilesItemTreeElement);
this._singleProfileMode = singleProfileMode;
this._profileTypesByIdMap = {};
this.profileViews = document.createElement("div");
this.profileViews.id = "profile-views";
this.splitView.mainElement.appendChild(this.profileViews);
this._statusBarButtons = [];
this.recordButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item");
this.recordButton.addEventListener("click", this.toggleRecordButton, this);
this._statusBarButtons.push(this.recordButton);
this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item");
this.clearResultsButton.addEventListener("click", this._clearProfiles, this);
this._statusBarButtons.push(this.clearResultsButton);
this._profileTypeStatusBarItemsContainer = document.createElement("div");
this._profileTypeStatusBarItemsContainer.className = "status-bar-items";
this._profileViewStatusBarItemsContainer = document.createElement("div");
this._profileViewStatusBarItemsContainer.className = "status-bar-items";
if (singleProfileMode) {
this._launcherView = this._createLauncherView();
this._registerProfileType( (type));
this._selectedProfileType = type;
this._updateProfileTypeSpecificUI();
} else {
this._launcherView = new WebInspector.MultiProfileLauncherView(this);
this._launcherView.addEventListener(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this);
this._registerProfileType(new WebInspector.CPUProfileType());
if (!WebInspector.WorkerManager.isWorkerFrontend())
this._registerProfileType(new WebInspector.CSSSelectorProfileType());
this._registerProfileType(new WebInspector.HeapSnapshotProfileType());
if (!WebInspector.WorkerManager.isWorkerFrontend() && WebInspector.experimentsSettings.nativeMemorySnapshots.isEnabled()) {
this._registerProfileType(new WebInspector.NativeSnapshotProfileType());
this._registerProfileType(new WebInspector.NativeMemoryProfileType());
}
if (!WebInspector.WorkerManager.isWorkerFrontend() && WebInspector.experimentsSettings.canvasInspection.isEnabled())
this._registerProfileType(new WebInspector.CanvasProfileType());
}
this._reset();
this._createFileSelectorElement();
this.element.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
WebInspector.ContextMenu.registerProvider(this);
}
WebInspector.ProfilesPanel.prototype = {
_createFileSelectorElement: function()
{
if (this._fileSelectorElement)
this.element.removeChild(this._fileSelectorElement);
this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this));
this.element.appendChild(this._fileSelectorElement);
},
_createLauncherView: function()
{
return new WebInspector.ProfileLauncherView(this);
},
_findProfileTypeByExtension: function(fileName)
{
for (var id in this._profileTypesByIdMap) {
var type = this._profileTypesByIdMap[id];
var extension = type.fileExtension();
if (!extension)
continue;
if (fileName.endsWith(type.fileExtension()))
return type;
}
return null;
},
_loadFromFile: function(file)
{
this._createFileSelectorElement();
var profileType = this._findProfileTypeByExtension(file.name);
if (!profileType) {
var extensions = [];
for (var id in this._profileTypesByIdMap) {
var extension = this._profileTypesByIdMap[id].fileExtension();
if (!extension)
continue;
extensions.push(extension);
}
WebInspector.log(WebInspector.UIString("Can't load file. Only files with extensions '%s' can be loaded.", extensions.join("', '")));
return;
}
if (!!profileType.findTemporaryProfile()) {
WebInspector.log(WebInspector.UIString("Can't load profile when other profile is recording."));
return;
}
var temporaryProfile = profileType.createTemporaryProfile(WebInspector.ProfilesPanelDescriptor.UserInitiatedProfileName + "." + file.name);
profileType.addProfile(temporaryProfile);
temporaryProfile._fromFile = true;
temporaryProfile.loadFromFile(file);
},
get statusBarItems()
{
return this._statusBarButtons.select("element").concat(this._profileTypeStatusBarItemsContainer, this._profileViewStatusBarItemsContainer);
},
toggleRecordButton: function()
{
var isProfiling = this._selectedProfileType.buttonClicked();
this.setRecordingProfile(this._selectedProfileType.id, isProfiling);
},
_populateAllProfiles: function()
{
if (this._profilesWereRequested)
return;
this._profilesWereRequested = true;
for (var typeId in this._profileTypesByIdMap)
this._profileTypesByIdMap[typeId]._populateProfiles();
},
wasShown: function()
{
WebInspector.Panel.prototype.wasShown.call(this);
this._populateAllProfiles();
},
_onProfileTypeSelected: function(event)
{
this._selectedProfileType = (event.data);
this._updateProfileTypeSpecificUI();
},
_updateProfileTypeSpecificUI: function()
{
this.recordButton.title = this._selectedProfileType.buttonTooltip;
this._profileTypeStatusBarItemsContainer.removeChildren();
var statusBarItems = this._selectedProfileType.statusBarItems;
if (statusBarItems) {
for (var i = 0; i < statusBarItems.length; ++i)
this._profileTypeStatusBarItemsContainer.appendChild(statusBarItems[i]);
}
this._resize(this.splitView.sidebarWidth());
},
_reset: function()
{
WebInspector.Panel.prototype.reset.call(this);
for (var typeId in this._profileTypesByIdMap)
this._profileTypesByIdMap[typeId]._reset();
delete this.visibleView;
delete this.currentQuery;
this.searchCanceled();
this._profileGroups = {};
this._profilesWereRequested = false;
this.recordButton.toggled = false;
if (this._selectedProfileType)
this.recordButton.title = this._selectedProfileType.buttonTooltip;
this._launcherView.profileFinished();
this.sidebarTreeElement.removeStyleClass("some-expandable");
this.profileViews.removeChildren();
this._profileViewStatusBarItemsContainer.removeChildren();
this.removeAllListeners();
this.recordButton.visible = true;
this._profileViewStatusBarItemsContainer.removeStyleClass("hidden");
this.clearResultsButton.element.removeStyleClass("hidden");
this.profilesItemTreeElement.select();
this._showLauncherView();
},
_showLauncherView: function()
{
this.closeVisibleView();
this._profileViewStatusBarItemsContainer.removeChildren();
this._launcherView.show(this.splitView.mainElement);
this.visibleView = this._launcherView;
},
_clearProfiles: function()
{
ProfilerAgent.clearProfiles();
HeapProfilerAgent.clearProfiles();
this._reset();
},
_garbageCollectButtonClicked: function()
{
HeapProfilerAgent.collectGarbage();
},
_registerProfileType: function(profileType)
{
this._profileTypesByIdMap[profileType.id] = profileType;
this._launcherView.addProfileType(profileType);
profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.treeItemTitle, null, true);
profileType.treeElement.hidden = !this._singleProfileMode;
this.sidebarTree.appendChild(profileType.treeElement);
profileType.treeElement.childrenListElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
function onAddProfileHeader(event)
{
this._addProfileHeader(event.data);
}
function onRemoveProfileHeader(event)
{
this._removeProfileHeader(event.data);
}
function onProgressUpdated(event)
{
this._reportProfileProgress(event.data.profile, event.data.done, event.data.total);
}
profileType.addEventListener(WebInspector.ProfileType.Events.ViewUpdated, this._updateProfileTypeSpecificUI, this);
profileType.addEventListener(WebInspector.ProfileType.Events.AddProfileHeader, onAddProfileHeader, this);
profileType.addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, onRemoveProfileHeader, this);
profileType.addEventListener(WebInspector.ProfileType.Events.ProgressUpdated, onProgressUpdated, this);
},
_handleContextMenuEvent: function(event)
{
var element = event.srcElement;
while (element && !element.treeElement && element !== this.element)
element = element.parentElement;
if (!element)
return;
if (element.treeElement && element.treeElement.handleContextMenuEvent) {
element.treeElement.handleContextMenuEvent(event, this);
return;
}
if (element !== this.element || event.srcElement === this.sidebarElement) {
var contextMenu = new WebInspector.ContextMenu(event);
if (this.visibleView instanceof WebInspector.HeapSnapshotView)
this.visibleView.populateContextMenu(contextMenu, event);
contextMenu.appendItem(WebInspector.UIString("Load\u2026"), this._fileSelectorElement.click.bind(this._fileSelectorElement));
contextMenu.show();
}
},
_makeTitleKey: function(text, profileTypeId)
{
return escape(text) + '/' + escape(profileTypeId);
},
_addProfileHeader: function(profile)
{
if (!profile.isTemporary)
this._removeTemporaryProfile(profile.profileType().id);
var profileType = profile.profileType();
var typeId = profileType.id;
var sidebarParent = profileType.treeElement;
sidebarParent.hidden = false;
var small = false;
var alternateTitle;
if (!WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(profile.title) && !profile.isTemporary) {
var profileTitleKey = this._makeTitleKey(profile.title, typeId);
if (!(profileTitleKey in this._profileGroups))
this._profileGroups[profileTitleKey] = [];
var group = this._profileGroups[profileTitleKey];
group.push(profile);
if (group.length === 2) {
group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(this, profile.title);
var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement);
sidebarParent.insertChild(group._profilesTreeElement, index);
var selected = group[0]._profilesTreeElement.selected;
sidebarParent.removeChild(group[0]._profilesTreeElement);
group._profilesTreeElement.appendChild(group[0]._profilesTreeElement);
if (selected)
group[0]._profilesTreeElement.revealAndSelect();
group[0]._profilesTreeElement.small = true;
group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);
this.sidebarTreeElement.addStyleClass("some-expandable");
}
if (group.length >= 2) {
sidebarParent = group._profilesTreeElement;
alternateTitle = WebInspector.UIString("Run %d", group.length);
small = true;
}
}
var profileTreeElement = profile.createSidebarTreeElement();
profile.sidebarElement = profileTreeElement;
profileTreeElement.small = small;
if (alternateTitle)
profileTreeElement.mainTitle = alternateTitle;
profile._profilesTreeElement = profileTreeElement;
sidebarParent.appendChild(profileTreeElement);
if (!profile.isTemporary) {
if (!this.visibleView)
this._showProfile(profile);
this.dispatchEventToListeners("profile added", {
type: typeId
});
}
},
_removeProfileHeader: function(profile)
{
profile.dispose();
profile.profileType().removeProfile(profile);
var sidebarParent = profile.profileType().treeElement;
var profileTitleKey = this._makeTitleKey(profile.title, profile.profileType().id);
var group = this._profileGroups[profileTitleKey];
if (group) {
group.splice(group.indexOf(profile), 1);
if (group.length === 1) {
var index = sidebarParent.children.indexOf(group._profilesTreeElement);
sidebarParent.insertChild(group[0]._profilesTreeElement, index);
group[0]._profilesTreeElement.small = false;
group[0]._profilesTreeElement.mainTitle = group[0].title;
sidebarParent.removeChild(group._profilesTreeElement);
}
if (group.length !== 0)
sidebarParent = group._profilesTreeElement;
else
delete this._profileGroups[profileTitleKey];
}
sidebarParent.removeChild(profile._profilesTreeElement);
if (!sidebarParent.children.length) {
this.profilesItemTreeElement.select();
this._showLauncherView();
sidebarParent.hidden = !this._singleProfileMode;
}
},
_showProfile: function(profile)
{
if (!profile || profile.isTemporary)
return null;
var view = profile.view(this);
if (view === this.visibleView)
return view;
this.closeVisibleView();
view.show(this.profileViews);
profile._profilesTreeElement._suppressOnSelect = true;
profile._profilesTreeElement.revealAndSelect();
delete profile._profilesTreeElement._suppressOnSelect;
this.visibleView = view;
this._profileViewStatusBarItemsContainer.removeChildren();
var statusBarItems = view.statusBarItems;
if (statusBarItems)
for (var i = 0; i < statusBarItems.length; ++i)
this._profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
return view;
},
showObject: function(snapshotObjectId, viewName)
{
var heapProfiles = this.getProfileType(WebInspector.HeapSnapshotProfileType.TypeId).getProfiles();
for (var i = 0; i < heapProfiles.length; i++) {
var profile = heapProfiles[i];
if (profile.maxJSObjectId >= snapshotObjectId) {
this._showProfile(profile);
var view = profile.view(this);
view.changeView(viewName, function() {
view.dataGrid.highlightObjectByHeapSnapshotId(snapshotObjectId);
});
break;
}
}
},
_createTemporaryProfile: function(typeId)
{
var type = this.getProfileType(typeId);
if (!type.findTemporaryProfile())
type.addProfile(type.createTemporaryProfile());
},
_removeTemporaryProfile: function(typeId)
{
var temporaryProfile = this.getProfileType(typeId).findTemporaryProfile();
if (!!temporaryProfile)
this._removeProfileHeader(temporaryProfile);
},
getProfile: function(typeId, uid)
{
return this.getProfileType(typeId).getProfile(uid);
},
showView: function(view)
{
this._showProfile(view.profile);
},
getProfileType: function(typeId)
{
return this._profileTypesByIdMap[typeId];
},
showProfile: function(typeId, uid)
{
return this._showProfile(this.getProfile(typeId, Number(uid)));
},
closeVisibleView: function()
{
if (this.visibleView)
this.visibleView.detach();
delete this.visibleView;
},
performSearch: function(query)
{
this.searchCanceled();
var searchableViews = this._searchableViews();
if (!searchableViews || !searchableViews.length)
return;
var visibleView = this.visibleView;
var matchesCountUpdateTimeout = null;
function updateMatchesCount()
{
WebInspector.searchController.updateSearchMatchesCount(this._totalSearchMatches, this);
WebInspector.searchController.updateCurrentMatchIndex(this._currentSearchResultIndex, this);
matchesCountUpdateTimeout = null;
}
function updateMatchesCountSoon()
{
if (matchesCountUpdateTimeout)
return;
matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500);
}
function finishedCallback(view, searchMatches)
{
if (!searchMatches)
return;
this._totalSearchMatches += searchMatches;
this._searchResults.push(view);
if (this.searchMatchFound)
this.searchMatchFound(view, searchMatches);
updateMatchesCountSoon.call(this);
if (view === visibleView)
view.jumpToFirstSearchResult();
}
var i = 0;
var panel = this;
var boundFinishedCallback = finishedCallback.bind(this);
var chunkIntervalIdentifier = null;
function processChunk()
{
var view = searchableViews[i];
if (++i >= searchableViews.length) {
if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
delete panel._currentSearchChunkIntervalIdentifier;
clearInterval(chunkIntervalIdentifier);
}
if (!view)
return;
view.currentQuery = query;
view.performSearch(query, boundFinishedCallback);
}
processChunk();
chunkIntervalIdentifier = setInterval(processChunk, 25);
this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
},
jumpToNextSearchResult: function()
{
if (!this.showView || !this._searchResults || !this._searchResults.length)
return;
var showFirstResult = false;
this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
if (this._currentSearchResultIndex === -1) {
this._currentSearchResultIndex = 0;
showFirstResult = true;
}
var currentView = this._searchResults[this._currentSearchResultIndex];
if (currentView.showingLastSearchResult()) {
if (++this._currentSearchResultIndex >= this._searchResults.length)
this._currentSearchResultIndex = 0;
currentView = this._searchResults[this._currentSearchResultIndex];
showFirstResult = true;
}
WebInspector.searchController.updateCurrentMatchIndex(this._currentSearchResultIndex, this);
if (currentView !== this.visibleView) {
this.showView(currentView);
WebInspector.searchController.showSearchField();
}
if (showFirstResult)
currentView.jumpToFirstSearchResult();
else
currentView.jumpToNextSearchResult();
},
jumpToPreviousSearchResult: function()
{
if (!this.showView || !this._searchResults || !this._searchResults.length)
return;
var showLastResult = false;
this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
if (this._currentSearchResultIndex === -1) {
this._currentSearchResultIndex = 0;
showLastResult = true;
}
var currentView = this._searchResults[this._currentSearchResultIndex];
if (currentView.showingFirstSearchResult()) {
if (--this._currentSearchResultIndex < 0)
this._currentSearchResultIndex = (this._searchResults.length - 1);
currentView = this._searchResults[this._currentSearchResultIndex];
showLastResult = true;
}
WebInspector.searchController.updateCurrentMatchIndex(this._currentSearchResultIndex, this);
if (currentView !== this.visibleView) {
this.showView(currentView);
WebInspector.searchController.showSearchField();
}
if (showLastResult)
currentView.jumpToLastSearchResult();
else
currentView.jumpToPreviousSearchResult();
},
_getAllProfiles: function()
{
var profiles = [];
for (var typeId in this._profileTypesByIdMap)
profiles = profiles.concat(this._profileTypesByIdMap[typeId].getProfiles());
return profiles;
},
_searchableViews: function()
{
var profiles = this._getAllProfiles();
var searchableViews = [];
for (var i = 0; i < profiles.length; ++i) {
var view = profiles[i].view(this);
if (view.performSearch)
searchableViews.push(view)
}
var index = searchableViews.indexOf(this.visibleView);
if (index > 0) {
searchableViews[index] = searchableViews[0];
searchableViews[0] = this.visibleView;
}
return searchableViews;
},
searchMatchFound: function(view, matches)
{
view.profile._profilesTreeElement.searchMatches = matches;
},
searchCanceled: function()
{
if (this._searchResults) {
for (var i = 0; i < this._searchResults.length; ++i) {
var view = this._searchResults[i];
if (view.searchCanceled)
view.searchCanceled();
delete view.currentQuery;
}
}
WebInspector.Panel.prototype.searchCanceled.call(this);
if (this._currentSearchChunkIntervalIdentifier) {
clearInterval(this._currentSearchChunkIntervalIdentifier);
delete this._currentSearchChunkIntervalIdentifier;
}
this._totalSearchMatches = 0;
this._currentSearchResultIndex = 0;
this._searchResults = [];
var profiles = this._getAllProfiles();
for (var i = 0; i < profiles.length; ++i)
profiles[i]._profilesTreeElement.searchMatches = 0;
},
sidebarResized: function(event)
{
var sidebarWidth = (event.data);
this._resize(sidebarWidth);
},
onResize: function()
{
this._resize(this.splitView.sidebarWidth());
},
_resize: function(sidebarWidth)
{
var lastItemElement = this._statusBarButtons[this._statusBarButtons.length - 1].element;
var left = lastItemElement.totalOffsetLeft() + lastItemElement.offsetWidth;
this._profileTypeStatusBarItemsContainer.style.left = left + "px";
left += this._profileTypeStatusBarItemsContainer.offsetWidth - 1;
this._profileViewStatusBarItemsContainer.style.left = Math.max(left, sidebarWidth) + "px";
},
setRecordingProfile: function(profileType, isProfiling)
{
var profileTypeObject = this.getProfileType(profileType);
this.recordButton.toggled = isProfiling;
this.recordButton.title = profileTypeObject.buttonTooltip;
if (isProfiling) {
this._launcherView.profileStarted();
this._createTemporaryProfile(profileType);
} else
this._launcherView.profileFinished();
},
_reportProfileProgress: function(profile, done, total)
{
profile.sidebarElement.subtitle = WebInspector.UIString("%.0f%", (done / total) * 100);
profile.sidebarElement.wait = true;
},
appendApplicableItems: function(event, contextMenu, target)
{
if (WebInspector.inspectorView.currentPanel() !== this)
return;
var object = (target);
var objectId = object.objectId;
if (!objectId)
return;
var heapProfiles = this.getProfileType(WebInspector.HeapSnapshotProfileType.TypeId).getProfiles();
if (!heapProfiles.length)
return;
function revealInView(viewName)
{
HeapProfilerAgent.getHeapObjectId(objectId, didReceiveHeapObjectId.bind(this, viewName));
}
function didReceiveHeapObjectId(viewName, error, result)
{
if (WebInspector.inspectorView.currentPanel() !== this)
return;
if (!error)
this.showObject(result, viewName);
}
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInView.bind(this, "Dominators"));
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInView.bind(this, "Summary"));
},
__proto__: WebInspector.Panel.prototype
}
WebInspector.ProfileSidebarTreeElement = function(profile, titleFormat, className)
{
this.profile = profile;
this._titleFormat = titleFormat;
if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(this.profile.title))
this._profileNumber = WebInspector.ProfilesPanelDescriptor.userInitiatedProfileIndex(this.profile.title);
WebInspector.SidebarTreeElement.call(this, className, "", "", profile, false);
this.refreshTitles();
}
WebInspector.ProfileSidebarTreeElement.prototype = {
onselect: function()
{
if (!this._suppressOnSelect)
this.treeOutline.panel._showProfile(this.profile);
},
ondelete: function()
{
this.treeOutline.panel._removeProfileHeader(this.profile);
return true;
},
get mainTitle()
{
if (this._mainTitle)
return this._mainTitle;
if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(this.profile.title))
return WebInspector.UIString(this._titleFormat, this._profileNumber);
return this.profile.title;
},
set mainTitle(x)
{
this._mainTitle = x;
this.refreshTitles();
},
set searchMatches(matches)
{
if (!matches) {
if (!this.bubbleElement)
return;
this.bubbleElement.removeStyleClass("search-matches");
this.bubbleText = "";
return;
}
this.bubbleText = matches;
this.bubbleElement.addStyleClass("search-matches");
},
handleContextMenuEvent: function(event, panel)
{
var profile = this.profile;
var contextMenu = new WebInspector.ContextMenu(event);
contextMenu.appendItem(WebInspector.UIString("Load\u2026"), panel._fileSelectorElement.click.bind(panel._fileSelectorElement));
if (profile.canSaveToFile())
contextMenu.appendItem(WebInspector.UIString("Save\u2026"), profile.saveToFile.bind(profile));
contextMenu.appendItem(WebInspector.UIString("Delete"), this.ondelete.bind(this));
contextMenu.show();
},
__proto__: WebInspector.SidebarTreeElement.prototype
}
WebInspector.ProfileGroupSidebarTreeElement = function(panel, title, subtitle)
{
WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
this._panel = panel;
}
WebInspector.ProfileGroupSidebarTreeElement.prototype = {
onselect: function()
{
if (this.children.length > 0)
this._panel._showProfile(this.children[this.children.length - 1].profile);
},
__proto__: WebInspector.SidebarTreeElement.prototype
}
WebInspector.ProfilesSidebarTreeElement = function(panel)
{
this._panel = panel;
this.small = false;
WebInspector.SidebarTreeElement.call(this, "profile-launcher-view-tree-item", WebInspector.UIString("Profiles"), "", null, false);
}
WebInspector.ProfilesSidebarTreeElement.prototype = {
onselect: function()
{
this._panel._showLauncherView();
},
get selectable()
{
return true;
},
__proto__: WebInspector.SidebarTreeElement.prototype
}
WebInspector.CPUProfilerPanel = function()
{
WebInspector.ProfilesPanel.call(this, "cpu-profiler", new WebInspector.CPUProfileType());
}
WebInspector.CPUProfilerPanel.prototype = {
__proto__: WebInspector.ProfilesPanel.prototype
}
WebInspector.CSSSelectorProfilerPanel = function()
{
WebInspector.ProfilesPanel.call(this, "css-profiler", new WebInspector.CSSSelectorProfileType());
}
WebInspector.CSSSelectorProfilerPanel.prototype = {
__proto__: WebInspector.ProfilesPanel.prototype
}
WebInspector.HeapProfilerPanel = function()
{
WebInspector.ProfilesPanel.call(this, "heap-profiler", new WebInspector.HeapSnapshotProfileType());
}
WebInspector.HeapProfilerPanel.prototype = {
__proto__: WebInspector.ProfilesPanel.prototype
}
WebInspector.CanvasProfilerPanel = function()
{
WebInspector.ProfilesPanel.call(this, "canvas-profiler", new WebInspector.CanvasProfileType());
}
WebInspector.CanvasProfilerPanel.prototype = {
__proto__: WebInspector.ProfilesPanel.prototype
}
WebInspector.MemoryChartProfilerPanel = function()
{
WebInspector.ProfilesPanel.call(this, "memory-chart-profiler", new WebInspector.NativeMemoryProfileType());
}
WebInspector.MemoryChartProfilerPanel.prototype = {
__proto__: WebInspector.ProfilesPanel.prototype
}
WebInspector.NativeMemoryProfilerPanel = function()
{
WebInspector.ProfilesPanel.call(this, "memory-snapshot-profiler", new WebInspector.NativeSnapshotProfileType());
}
WebInspector.NativeMemoryProfilerPanel.prototype = {
__proto__: WebInspector.ProfilesPanel.prototype
}
WebInspector.ProfileDataGridNode = function(profileNode, owningTree, hasChildren)
{
this.profileNode = profileNode;
WebInspector.DataGridNode.call(this, null, hasChildren);
this.tree = owningTree;
this.childrenByCallUID = {};
this.lastComparator = null;
this.callUID = profileNode.callUID;
this.selfTime = profileNode.selfTime;
this.totalTime = profileNode.totalTime;
this.functionName = profileNode.functionName;
this.numberOfCalls = profileNode.numberOfCalls;
this.url = profileNode.url;
}
WebInspector.ProfileDataGridNode.prototype = {
get data()
{
function formatMilliseconds(time)
{
return WebInspector.UIString("%.0f\u2009ms", time);
}
var data = {};
data["function"] = this.functionName;
data["calls"] = this.numberOfCalls;
if (this.tree.profileView.showSelfTimeAsPercent.get())
data["self"] = WebInspector.UIString("%.2f%", this.selfPercent);
else
data["self"] = formatMilliseconds(this.selfTime);
if (this.tree.profileView.showTotalTimeAsPercent.get())
data["total"] = WebInspector.UIString("%.2f%", this.totalPercent);
else
data["total"] = formatMilliseconds(this.totalTime);
if (this.tree.profileView.showAverageTimeAsPercent.get())
data["average"] = WebInspector.UIString("%.2f%", this.averagePercent);
else
data["average"] = formatMilliseconds(this.averageTime);
return data;
},
createCell: function(columnIdentifier)
{
var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
if (columnIdentifier === "self" && this._searchMatchedSelfColumn)
cell.addStyleClass("highlight");
else if (columnIdentifier === "total" && this._searchMatchedTotalColumn)
cell.addStyleClass("highlight");
else if (columnIdentifier === "average" && this._searchMatchedAverageColumn)
cell.addStyleClass("highlight");
else if (columnIdentifier === "calls" && this._searchMatchedCallsColumn)
cell.addStyleClass("highlight");
if (columnIdentifier !== "function")
return cell;
if (this.profileNode._searchMatchedFunctionColumn)
cell.addStyleClass("highlight");
if (this.profileNode.url) {
var lineNumber = this.profileNode.lineNumber ? this.profileNode.lineNumber - 1 : 0;
var urlElement = this.tree.profileView._linkifier.linkifyLocation(this.profileNode.url, lineNumber, 0, "profile-node-file");
urlElement.style.maxWidth = "75%";
cell.insertBefore(urlElement, cell.firstChild);
}
return cell;
},
select: function(supressSelectedEvent)
{
WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent);
this.tree.profileView._dataGridNodeSelected(this);
},
deselect: function(supressDeselectedEvent)
{
WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent);
this.tree.profileView._dataGridNodeDeselected(this);
},
sort: function(comparator, force)
{
var gridNodeGroups = [[this]];
for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) {
var gridNodes = gridNodeGroups[gridNodeGroupIndex];
var count = gridNodes.length;
for (var index = 0; index < count; ++index) {
var gridNode = gridNodes[index];
if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) {
if (gridNode.children.length)
gridNode.shouldRefreshChildren = true;
continue;
}
gridNode.lastComparator = comparator;
var children = gridNode.children;
var childCount = children.length;
if (childCount) {
children.sort(comparator);
for (var childIndex = 0; childIndex < childCount; ++childIndex)
children[childIndex]._recalculateSiblings(childIndex);
gridNodeGroups.push(children);
}
}
}
},
insertChild: function(profileDataGridNode, index)
{
WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index);
this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode;
},
removeChild: function(profileDataGridNode)
{
WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode);
delete this.childrenByCallUID[profileDataGridNode.callUID];
},
removeChildren: function()
{
WebInspector.DataGridNode.prototype.removeChildren.call(this);
this.childrenByCallUID = {};
},
findChild: function(node)
{
if (!node)
return null;
return this.childrenByCallUID[node.callUID];
},
get averageTime()
{
return this.selfTime / Math.max(1, this.numberOfCalls);
},
get averagePercent()
{
return this.averageTime / this.tree.totalTime * 100.0;
},
get selfPercent()
{
return this.selfTime / this.tree.totalTime * 100.0;
},
get totalPercent()
{
return this.totalTime / this.tree.totalTime * 100.0;
},
get _parent()
{
return this.parent !== this.dataGrid ? this.parent : this.tree;
},
populate: function()
{
if (this._populated)
return;
this._populated = true;
this._sharedPopulate();
if (this._parent) {
var currentComparator = this._parent.lastComparator;
if (currentComparator)
this.sort(currentComparator, true);
}
},
_save: function()
{
if (this._savedChildren)
return;
this._savedSelfTime = this.selfTime;
this._savedTotalTime = this.totalTime;
this._savedNumberOfCalls = this.numberOfCalls;
this._savedChildren = this.children.slice();
},
_restore: function()
{
if (!this._savedChildren)
return;
this.selfTime = this._savedSelfTime;
this.totalTime = this._savedTotalTime;
this.numberOfCalls = this._savedNumberOfCalls;
this.removeChildren();
var children = this._savedChildren;
var count = children.length;
for (var index = 0; index < count; ++index) {
children[index]._restore();
this.appendChild(children[index]);
}
},
_merge: function(child, shouldAbsorb)
{
this.selfTime += child.selfTime;
if (!shouldAbsorb) {
this.totalTime += child.totalTime;
this.numberOfCalls += child.numberOfCalls;
}
var children = this.children.slice();
this.removeChildren();
var count = children.length;
for (var index = 0; index < count; ++index) {
if (!shouldAbsorb || children[index] !== child)
this.appendChild(children[index]);
}
children = child.children.slice();
count = children.length;
for (var index = 0; index < count; ++index) {
var orphanedChild = children[index],
existingChild = this.childrenByCallUID[orphanedChild.callUID];
if (existingChild)
existingChild._merge(orphanedChild, false);
else
this.appendChild(orphanedChild);
}
},
__proto__: WebInspector.DataGridNode.prototype
}
WebInspector.ProfileDataGridTree = function(profileView, rootProfileNode)
{
this.tree = this;
this.children = [];
this.profileView = profileView;
this.totalTime = rootProfileNode.totalTime;
this.lastComparator = null;
this.childrenByCallUID = {};
}
WebInspector.ProfileDataGridTree.prototype = {
get expanded()
{
return true;
},
appendChild: function(child)
{
this.insertChild(child, this.children.length);
},
insertChild: function(child, index)
{
this.children.splice(index, 0, child);
this.childrenByCallUID[child.callUID] = child;
},
removeChildren: function()
{
this.children = [];
this.childrenByCallUID = {};
},
findChild: WebInspector.ProfileDataGridNode.prototype.findChild,
sort: WebInspector.ProfileDataGridNode.prototype.sort,
_save: function()
{
if (this._savedChildren)
return;
this._savedTotalTime = this.totalTime;
this._savedChildren = this.children.slice();
},
restore: function()
{
if (!this._savedChildren)
return;
this.children = this._savedChildren;
this.totalTime = this._savedTotalTime;
var children = this.children;
var count = children.length;
for (var index = 0; index < count; ++index)
children[index]._restore();
this._savedChildren = null;
}
}
WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}];
WebInspector.ProfileDataGridTree.propertyComparator = function(property, isAscending)
{
var comparator = WebInspector.ProfileDataGridTree.propertyComparators[(isAscending ? 1 : 0)][property];
if (!comparator) {
if (isAscending) {
comparator = function(lhs, rhs)
{
if (lhs[property] < rhs[property])
return -1;
if (lhs[property] > rhs[property])
return 1;
return 0;
}
} else {
comparator = function(lhs, rhs)
{
if (lhs[property] > rhs[property])
return -1;
if (lhs[property] < rhs[property])
return 1;
return 0;
}
}
WebInspector.ProfileDataGridTree.propertyComparators[(isAscending ? 1 : 0)][property] = comparator;
}
return comparator;
}
;
WebInspector.BottomUpProfileDataGridNode = function(profileNode, owningTree)
{
WebInspector.ProfileDataGridNode.call(this, profileNode, owningTree, this._willHaveChildren(profileNode));
this._remainingNodeInfos = [];
}
WebInspector.BottomUpProfileDataGridNode.prototype = {
_takePropertiesFromProfileDataGridNode: function(profileDataGridNode)
{
this._save();
this.selfTime = profileDataGridNode.selfTime;
this.totalTime = profileDataGridNode.totalTime;
this.numberOfCalls = profileDataGridNode.numberOfCalls;
},
_keepOnlyChild: function(child)
{
this._save();
this.removeChildren();
this.appendChild(child);
},
_exclude: function(aCallUID)
{
if (this._remainingNodeInfos)
this.populate();
this._save();
var children = this.children;
var index = this.children.length;
while (index--)
children[index]._exclude(aCallUID);
var child = this.childrenByCallUID[aCallUID];
if (child)
this._merge(child, true);
},
_restore: function()
{
WebInspector.ProfileDataGridNode.prototype._restore();
if (!this.children.length)
this.hasChildren = this._willHaveChildren(this.profileNode);
},
_merge: function(child, shouldAbsorb)
{
this.selfTime -= child.selfTime;
WebInspector.ProfileDataGridNode.prototype._merge.call(this, child, shouldAbsorb);
},
_sharedPopulate: function()
{
var remainingNodeInfos = this._remainingNodeInfos;
var count = remainingNodeInfos.length;
for (var index = 0; index < count; ++index) {
var nodeInfo = remainingNodeInfos[index];
var ancestor = nodeInfo.ancestor;
var focusNode = nodeInfo.focusNode;
var child = this.findChild(ancestor);
if (child) {
var totalTimeAccountedFor = nodeInfo.totalTimeAccountedFor;
child.selfTime += focusNode.selfTime;
child.numberOfCalls += focusNode.numberOfCalls;
if (!totalTimeAccountedFor)
child.totalTime += focusNode.totalTime;
} else {
child = new WebInspector.BottomUpProfileDataGridNode(ancestor, this.tree);
if (ancestor !== focusNode) {
child.selfTime = focusNode.selfTime;
child.totalTime = focusNode.totalTime;
child.numberOfCalls = focusNode.numberOfCalls;
}
this.appendChild(child);
}
var parent = ancestor.parent;
if (parent && parent.parent) {
nodeInfo.ancestor = parent;
child._remainingNodeInfos.push(nodeInfo);
}
}
delete this._remainingNodeInfos;
},
_willHaveChildren: function(profileNode)
{
return !!(profileNode.parent && profileNode.parent.parent);
},
__proto__: WebInspector.ProfileDataGridNode.prototype
}
WebInspector.BottomUpProfileDataGridTree = function(profileView, rootProfileNode)
{
WebInspector.ProfileDataGridTree.call(this, profileView, rootProfileNode);
var profileNodeUIDs = 0;
var profileNodeGroups = [[], [rootProfileNode]];
var visitedProfileNodesForCallUID = {};
this._remainingNodeInfos = [];
for (var profileNodeGroupIndex = 0; profileNodeGroupIndex < profileNodeGroups.length; ++profileNodeGroupIndex) {
var parentProfileNodes = profileNodeGroups[profileNodeGroupIndex];
var profileNodes = profileNodeGroups[++profileNodeGroupIndex];
var count = profileNodes.length;
for (var index = 0; index < count; ++index) {
var profileNode = profileNodes[index];
if (!profileNode.UID)
profileNode.UID = ++profileNodeUIDs;
if (profileNode.head && profileNode !== profileNode.head) {
var visitedNodes = visitedProfileNodesForCallUID[profileNode.callUID];
var totalTimeAccountedFor = false;
if (!visitedNodes) {
visitedNodes = {}
visitedProfileNodesForCallUID[profileNode.callUID] = visitedNodes;
} else {
var parentCount = parentProfileNodes.length;
for (var parentIndex = 0; parentIndex < parentCount; ++parentIndex) {
if (visitedNodes[parentProfileNodes[parentIndex].UID]) {
totalTimeAccountedFor = true;
break;
}
}
}
visitedNodes[profileNode.UID] = true;
this._remainingNodeInfos.push({ ancestor:profileNode, focusNode:profileNode, totalTimeAccountedFor:totalTimeAccountedFor });
}
var children = profileNode.children;
if (children.length) {
profileNodeGroups.push(parentProfileNodes.concat([profileNode]))
profileNodeGroups.push(children);
}
}
}
var any = (this);
var node = (any);
WebInspector.BottomUpProfileDataGridNode.prototype.populate.call(node);
return this;
}
WebInspector.BottomUpProfileDataGridTree.prototype = {
focus: function(profileDataGridNode)
{
if (!profileDataGridNode)
return;
this._save();
var currentNode = profileDataGridNode;
var focusNode = profileDataGridNode;
while (currentNode.parent && (currentNode instanceof WebInspector.ProfileDataGridNode)) {
currentNode._takePropertiesFromProfileDataGridNode(profileDataGridNode);
focusNode = currentNode;
currentNode = currentNode.parent;
if (currentNode instanceof WebInspector.ProfileDataGridNode)
currentNode._keepOnlyChild(focusNode);
}
this.children = [focusNode];
this.totalTime = profileDataGridNode.totalTime;
},
exclude: function(profileDataGridNode)
{
if (!profileDataGridNode)
return;
this._save();
var excludedCallUID = profileDataGridNode.callUID;
var excludedTopLevelChild = this.childrenByCallUID[excludedCallUID];
if (excludedTopLevelChild)
this.children.remove(excludedTopLevelChild);
var children = this.children;
var count = children.length;
for (var index = 0; index < count; ++index)
children[index]._exclude(excludedCallUID);
if (this.lastComparator)
this.sort(this.lastComparator, true);
},
_sharedPopulate: WebInspector.BottomUpProfileDataGridNode.prototype._sharedPopulate,
__proto__: WebInspector.ProfileDataGridTree.prototype
}
;
WebInspector.CPUProfileView = function(profileHeader)
{
WebInspector.View.call(this);
this.element.addStyleClass("profile-view");
this.showSelfTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowSelfTimeAsPercent", true);
this.showTotalTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowTotalTimeAsPercent", true);
this.showAverageTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowAverageTimeAsPercent", true);
this._viewType = WebInspector.settings.createSetting("cpuProfilerView", WebInspector.CPUProfileView._TypeHeavy);
var columns = [];
columns.push({id: "self", title: WebInspector.UIString("Self"), width: "72px", sort: WebInspector.DataGrid.Order.Descending, sortable: true});
columns.push({id: "total", title: WebInspector.UIString("Total"), width: "72px", sortable: true});
columns.push({id: "function", title: WebInspector.UIString("Function"), disclosure: true, sortable: true});
this.dataGrid = new WebInspector.DataGrid(columns);
this.dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortProfile, this);
this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
if (WebInspector.experimentsSettings.cpuFlameChart.isEnabled()) {
this._splitView = new WebInspector.SplitView(false, "flameChartSplitLocation");
this._splitView.show(this.element);
this._flameChart = new WebInspector.FlameChart(this);
this._flameChart.addEventListener(WebInspector.FlameChart.Events.SelectedNode, this._revealProfilerNode.bind(this));
this._flameChart.show(this._splitView.firstElement());
this.dataGrid.show(this._splitView.secondElement());
} else
this.dataGrid.show(this.element);
this.viewSelectComboBox = new WebInspector.StatusBarComboBox(this._changeView.bind(this));
var heavyViewOption = this.viewSelectComboBox.createOption(WebInspector.UIString("Heavy (Bottom Up)"), "", WebInspector.CPUProfileView._TypeHeavy);
var treeViewOption = this.viewSelectComboBox.createOption(WebInspector.UIString("Tree (Top Down)"), "", WebInspector.CPUProfileView._TypeTree);
this.viewSelectComboBox.select(this._viewType.get() === WebInspector.CPUProfileView._TypeHeavy ? heavyViewOption : treeViewOption);
this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item");
this.percentButton.addEventListener("click", this._percentClicked, this);
this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item");
this.focusButton.setEnabled(false);
this.focusButton.addEventListener("click", this._focusClicked, this);
this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item");
this.excludeButton.setEnabled(false);
this.excludeButton.addEventListener("click", this._excludeClicked, this);
this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item");
this.resetButton.visible = false;
this.resetButton.addEventListener("click", this._resetClicked, this);
this.profileHead = (null);
this.profileHeader = profileHeader;
this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultFormatter(30));
if (this.profileHeader._profile)
this._processProfileData(this.profileHeader._profile);
else
ProfilerAgent.getCPUProfile(this.profileHeader.uid, this._getCPUProfileCallback.bind(this));
}
WebInspector.CPUProfileView._TypeTree = "Tree";
WebInspector.CPUProfileView._TypeHeavy = "Heavy";
WebInspector.CPUProfileView.prototype = {
selectRange: function(timeLeft, timeRight)
{
if (!this._flameChart)
return;
this._flameChart.selectRange(timeLeft, timeRight);
},
_revealProfilerNode: function(event)
{
var current = this.profileDataGridTree.children[0];
while (current && current.profileNode !== event.data)
current = current.traverseNextNode(false, null, false);
if (current)
current.revealAndSelect();
},
_getCPUProfileCallback: function(error, profile)
{
if (error)
return;
if (!profile.head) {
return;
}
this._processProfileData(profile);
},
_processProfileData: function(profile)
{
this.profileHead = profile.head;
this.samples = profile.samples;
if (profile.idleTime)
this._injectIdleTimeNode(profile);
this._assignParentsInProfile();
if (this.samples)
this._buildIdToNodeMap();
this._changeView();
this._updatePercentButton();
if (this._flameChart)
this._flameChart.update();
},
get statusBarItems()
{
return [this.viewSelectComboBox.element, this.percentButton.element, this.focusButton.element, this.excludeButton.element, this.resetButton.element];
},
_getBottomUpProfileDataGridTree: function()
{
if (!this._bottomUpProfileDataGridTree)
this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, this.profileHead);
return this._bottomUpProfileDataGridTree;
},
_getTopDownProfileDataGridTree: function()
{
if (!this._topDownProfileDataGridTree)
this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, this.profileHead);
return this._topDownProfileDataGridTree;
},
willHide: function()
{
this._currentSearchResultIndex = -1;
},
refresh: function()
{
var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null;
this.dataGrid.rootNode().removeChildren();
var children = this.profileDataGridTree.children;
var count = children.length;
for (var index = 0; index < count; ++index)
this.dataGrid.rootNode().appendChild(children[index]);
if (selectedProfileNode)
selectedProfileNode.selected = true;
},
refreshVisibleData: function()
{
var child = this.dataGrid.rootNode().children[0];
while (child) {
child.refresh();
child = child.traverseNextNode(false, null, true);
}
},
refreshShowAsPercents: function()
{
this._updatePercentButton();
this.refreshVisibleData();
},
searchCanceled: function()
{
if (this._searchResults) {
for (var i = 0; i < this._searchResults.length; ++i) {
var profileNode = this._searchResults[i].profileNode;
delete profileNode._searchMatchedSelfColumn;
delete profileNode._searchMatchedTotalColumn;
delete profileNode._searchMatchedAverageColumn;
delete profileNode._searchMatchedCallsColumn;
delete profileNode._searchMatchedFunctionColumn;
profileNode.refresh();
}
}
delete this._searchFinishedCallback;
this._currentSearchResultIndex = -1;
this._searchResults = [];
},
performSearch: function(query, finishedCallback)
{
this.searchCanceled();
query = query.trim();
if (!query.length)
return;
this._searchFinishedCallback = finishedCallback;
var greaterThan = (query.startsWith(">"));
var lessThan = (query.startsWith("<"));
var equalTo = (query.startsWith("=") || ((greaterThan || lessThan) && query.indexOf("=") === 1));
var percentUnits = (query.lastIndexOf("%") === (query.length - 1));
var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2));
var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1));
var queryNumber = parseFloat(query);
if (greaterThan || lessThan || equalTo) {
if (equalTo && (greaterThan || lessThan))
queryNumber = parseFloat(query.substring(2));
else
queryNumber = parseFloat(query.substring(1));
}
var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber);
if (!isNaN(queryNumber) && !(greaterThan || lessThan))
equalTo = true;
var matcher = new RegExp(query.escapeForRegExp(), "i");
function matchesQuery( profileDataGridNode)
{
delete profileDataGridNode._searchMatchedSelfColumn;
delete profileDataGridNode._searchMatchedTotalColumn;
delete profileDataGridNode._searchMatchedAverageColumn;
delete profileDataGridNode._searchMatchedCallsColumn;
delete profileDataGridNode._searchMatchedFunctionColumn;
if (percentUnits) {
if (lessThan) {
if (profileDataGridNode.selfPercent < queryNumber)
profileDataGridNode._searchMatchedSelfColumn = true;
if (profileDataGridNode.totalPercent < queryNumber)
profileDataGridNode._searchMatchedTotalColumn = true;
if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
profileDataGridNode._searchMatchedAverageColumn = true;
} else if (greaterThan) {
if (profileDataGridNode.selfPercent > queryNumber)
profileDataGridNode._searchMatchedSelfColumn = true;
if (profileDataGridNode.totalPercent > queryNumber)
profileDataGridNode._searchMatchedTotalColumn = true;
if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
profileDataGridNode._searchMatchedAverageColumn = true;
}
if (equalTo) {
if (profileDataGridNode.selfPercent == queryNumber)
profileDataGridNode._searchMatchedSelfColumn = true;
if (profileDataGridNode.totalPercent == queryNumber)
profileDataGridNode._searchMatchedTotalColumn = true;
if (profileDataGridNode.averagePercent < queryNumberMilliseconds)
profileDataGridNode._searchMatchedAverageColumn = true;
}
} else if (millisecondsUnits || secondsUnits) {
if (lessThan) {
if (profileDataGridNode.selfTime < queryNumberMilliseconds)
profileDataGridNode._searchMatchedSelfColumn = true;
if (profileDataGridNode.totalTime < queryNumberMilliseconds)
profileDataGridNode._searchMatchedTotalColumn = true;
if (profileDataGridNode.averageTime < queryNumberMilliseconds)
profileDataGridNode._searchMatchedAverageColumn = true;
} else if (greaterThan) {
if (profileDataGridNode.selfTime > queryNumberMilliseconds)
profileDataGridNode._searchMatchedSelfColumn = true;
if (profileDataGridNode.totalTime > queryNumberMilliseconds)
profileDataGridNode._searchMatchedTotalColumn = true;
if (profileDataGridNode.averageTime > queryNumberMilliseconds)
profileDataGridNode._searchMatchedAverageColumn = true;
}
if (equalTo) {
if (profileDataGridNode.selfTime == queryNumberMilliseconds)
profileDataGridNode._searchMatchedSelfColumn = true;
if (profileDataGridNode.totalTime == queryNumberMilliseconds)
profileDataGridNode._searchMatchedTotalColumn = true;
if (profileDataGridNode.averageTime == queryNumberMilliseconds)
profileDataGridNode._searchMatchedAverageColumn = true;
}
} else {
if (equalTo && profileDataGridNode.numberOfCalls == queryNumber)
profileDataGridNode._searchMatchedCallsColumn = true;
if (greaterThan && profileDataGridNode.numberOfCalls > queryNumber)
profileDataGridNode._searchMatchedCallsColumn = true;
if (lessThan && profileDataGridNode.numberOfCalls < queryNumber)
profileDataGridNode._searchMatchedCallsColumn = true;
}
if (profileDataGridNode.functionName.match(matcher) || (profileDataGridNode.url && profileDataGridNode.url.match(matcher)))
profileDataGridNode._searchMatchedFunctionColumn = true;
if (profileDataGridNode._searchMatchedSelfColumn ||
profileDataGridNode._searchMatchedTotalColumn ||
profileDataGridNode._searchMatchedAverageColumn ||
profileDataGridNode._searchMatchedCallsColumn ||
profileDataGridNode._searchMatchedFunctionColumn)
{
profileDataGridNode.refresh();
return true;
}
return false;
}
var current = this.profileDataGridTree.children[0];
while (current) {
if (matchesQuery(current)) {
this._searchResults.push({ profileNode: current });
}
current = current.traverseNextNode(false, null, false);
}
finishedCallback(this, this._searchResults.length);
},
jumpToFirstSearchResult: function()
{
if (!this._searchResults || !this._searchResults.length)
return;
this._currentSearchResultIndex = 0;
this._jumpToSearchResult(this._currentSearchResultIndex);
},
jumpToLastSearchResult: function()
{
if (!this._searchResults || !this._searchResults.length)
return;
this._currentSearchResultIndex = (this._searchResults.length - 1);
this._jumpToSearchResult(this._currentSearchResultIndex);
},
jumpToNextSearchResult: function()
{
if (!this._searchResults || !this._searchResults.length)
return;
if (++this._currentSearchResultIndex >= this._searchResults.length)
this._currentSearchResultIndex = 0;
this._jumpToSearchResult(this._currentSearchResultIndex);
},
jumpToPreviousSearchResult: function()
{
if (!this._searchResults || !this._searchResults.length)
return;
if (--this._currentSearchResultIndex < 0)
this._currentSearchResultIndex = (this._searchResults.length - 1);
this._jumpToSearchResult(this._currentSearchResultIndex);
},
showingFirstSearchResult: function()
{
return (this._currentSearchResultIndex === 0);
},
showingLastSearchResult: function()
{
return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
},
_jumpToSearchResult: function(index)
{
var searchResult = this._searchResults[index];
if (!searchResult)
return;
var profileNode = searchResult.profileNode;
profileNode.revealAndSelect();
},
_changeView: function()
{
if (!this.profileHeader)
return;
switch (this.viewSelectComboBox.selectedOption().value) {
case WebInspector.CPUProfileView._TypeTree:
this.profileDataGridTree = this._getTopDownProfileDataGridTree();
this._sortProfile();
this._viewType.set(WebInspector.CPUProfileView._TypeTree);
break;
case WebInspector.CPUProfileView._TypeHeavy:
this.profileDataGridTree = this._getBottomUpProfileDataGridTree();
this._sortProfile();
this._viewType.set(WebInspector.CPUProfileView._TypeHeavy);
}
if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
return;
this._searchFinishedCallback(this, -this._searchResults.length);
this.performSearch(this.currentQuery, this._searchFinishedCallback);
},
_percentClicked: function(event)
{
var currentState = this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get();
this.showSelfTimeAsPercent.set(!currentState);
this.showTotalTimeAsPercent.set(!currentState);
this.showAverageTimeAsPercent.set(!currentState);
this.refreshShowAsPercents();
},
_updatePercentButton: function()
{
if (this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get()) {
this.percentButton.title = WebInspector.UIString("Show absolute total and self times.");
this.percentButton.toggled = true;
} else {
this.percentButton.title = WebInspector.UIString("Show total and self times as percentages.");
this.percentButton.toggled = false;
}
},
_focusClicked: function(event)
{
if (!this.dataGrid.selectedNode)
return;
this.resetButton.visible = true;
this.profileDataGridTree.focus(this.dataGrid.selectedNode);
this.refresh();
this.refreshVisibleData();
},
_excludeClicked: function(event)
{
var selectedNode = this.dataGrid.selectedNode
if (!selectedNode)
return;
selectedNode.deselect();
this.resetButton.visible = true;
this.profileDataGridTree.exclude(selectedNode);
this.refresh();
this.refreshVisibleData();
},
_resetClicked: function(event)
{
this.resetButton.visible = false;
this.profileDataGridTree.restore();
this._linkifier.reset();
this.refresh();
this.refreshVisibleData();
},
_dataGridNodeSelected: function(node)
{
this.focusButton.setEnabled(true);
this.excludeButton.setEnabled(true);
},
_dataGridNodeDeselected: function(node)
{
this.focusButton.setEnabled(false);
this.excludeButton.setEnabled(false);
},
_sortProfile: function()
{
var sortAscending = this.dataGrid.isSortOrderAscending();
var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier();
var sortProperty = {
"average": "averageTime",
"self": "selfTime",
"total": "totalTime",
"calls": "numberOfCalls",
"function": "functionName"
}[sortColumnIdentifier];
this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending));
this.refresh();
},
_mouseDownInDataGrid: function(event)
{
if (event.detail < 2)
return;
var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column") && !cell.hasStyleClass("average-column")))
return;
if (cell.hasStyleClass("total-column"))
this.showTotalTimeAsPercent.set(!this.showTotalTimeAsPercent.get());
else if (cell.hasStyleClass("self-column"))
this.showSelfTimeAsPercent.set(!this.showSelfTimeAsPercent.get());
else if (cell.hasStyleClass("average-column"))
this.showAverageTimeAsPercent.set(!this.showAverageTimeAsPercent.get());
this.refreshShowAsPercents();
event.consume(true);
},
_assignParentsInProfile: function()
{
var head = this.profileHead;
head.parent = null;
head.head = null;
var nodesToTraverse = [ { parent: head, children: head.children } ];
while (nodesToTraverse.length > 0) {
var pair = nodesToTraverse.pop();
var parent = pair.parent;
var children = pair.children;
var length = children.length;
for (var i = 0; i < length; ++i) {
children[i].head = head;
children[i].parent = parent;
if (children[i].children.length > 0)
nodesToTraverse.push({ parent: children[i], children: children[i].children });
}
}
},
_buildIdToNodeMap: function()
{
var idToNode = this._idToNode = {};
var stack = [this.profileHead];
while (stack.length) {
var node = stack.pop();
idToNode[node.id] = node;
for (var i = 0; i < node.children.length; i++)
stack.push(node.children[i]);
}
},
_injectIdleTimeNode: function(profile)
{
var idleTime = profile.idleTime;
var nodes = profile.head.children;
var programNode = {selfTime: 0};
for (var i = nodes.length - 1; i >= 0; --i) {
if (nodes[i].functionName === "(program)") {
programNode = nodes[i];
break;
}
}
var programTime = programNode.selfTime;
if (idleTime > programTime)
idleTime = programTime;
programTime = programTime - idleTime;
programNode.selfTime = programTime;
programNode.totalTime = programTime;
var idleNode = {
functionName: "(idle)",
url: null,
lineNumber: 0,
totalTime: idleTime,
selfTime: idleTime,
numberOfCalls: 0,
visible: true,
callUID: 0,
children: []
};
nodes.push(idleNode);
},
__proto__: WebInspector.View.prototype
}
WebInspector.CPUProfileType = function()
{
WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile"));
InspectorBackend.registerProfilerDispatcher(this);
this._recording = false;
WebInspector.CPUProfileType.instance = this;
}
WebInspector.CPUProfileType.TypeId = "CPU";
WebInspector.CPUProfileType.prototype = {
fileExtension: function()
{
return ".cpuprofile";
},
get buttonTooltip()
{
return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling.");
},
buttonClicked: function()
{
if (this._recording) {
this.stopRecordingProfile();
return false;
} else {
this.startRecordingProfile();
return true;
}
},
get treeItemTitle()
{
return WebInspector.UIString("CPU PROFILES");
},
get description()
{
return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions.");
},
addProfileHeader: function(profileHeader)
{
this.addProfile(this.createProfile(profileHeader));
},
isRecordingProfile: function()
{
return this._recording;
},
startRecordingProfile: function()
{
this._recording = true;
WebInspector.userMetrics.ProfilesCPUProfileTaken.record();
ProfilerAgent.start();
},
stopRecordingProfile: function()
{
this._recording = false;
ProfilerAgent.stop();
},
setRecordingProfile: function(isProfiling)
{
this._recording = isProfiling;
},
createTemporaryProfile: function(title)
{
title = title || WebInspector.UIString("Recording\u2026");
return new WebInspector.CPUProfileHeader(this, title);
},
createProfile: function(profile)
{
return new WebInspector.CPUProfileHeader(this, profile.title, profile.uid);
},
removeProfile: function(profile)
{
WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
if (!profile.isTemporary)
ProfilerAgent.removeProfile(this.id, profile.uid);
},
_requestProfilesFromBackend: function(populateCallback)
{
ProfilerAgent.getProfileHeaders(populateCallback);
},
resetProfiles: function()
{
this._reset();
},
addHeapSnapshotChunk: function(uid, chunk)
{
throw new Error("Never called");
},
finishHeapSnapshot: function(uid)
{
throw new Error("Never called");
},
reportHeapSnapshotProgress: function(done, total)
{
throw new Error("Never called");
},
__proto__: WebInspector.ProfileType.prototype
}
WebInspector.CPUProfileHeader = function(type, title, uid)
{
WebInspector.ProfileHeader.call(this, type, title, uid);
}
WebInspector.CPUProfileHeader.prototype = {
onTransferStarted: function()
{
this._jsonifiedProfile = "";
this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026 %s", Number.bytesToString(this._jsonifiedProfile.length));
},
onChunkTransferred: function(reader)
{
this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026 %d\%", Number.bytesToString(this._jsonifiedProfile.length));
},
onTransferFinished: function()
{
this.sidebarElement.subtitle = WebInspector.UIString("Parsing\u2026");
this._profile = JSON.parse(this._jsonifiedProfile);
this._jsonifiedProfile = null;
this.sidebarElement.subtitle = WebInspector.UIString("Loaded");
this.isTemporary = false;
},
onError: function(reader, e)
{
switch(e.target.error.code) {
case e.target.error.NOT_FOUND_ERR:
this.sidebarElement.subtitle = WebInspector.UIString("'%s' not found.", reader.fileName());
break;
case e.target.error.NOT_READABLE_ERR:
this.sidebarElement.subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName());
break;
case e.target.error.ABORT_ERR:
break;
default:
this.sidebarElement.subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code);
}
},
write: function(text)
{
this._jsonifiedProfile += text;
},
close: function() { },
createSidebarTreeElement: function()
{
return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Profile %d"), "profile-sidebar-tree-item");
},
createView: function(profilesPanel)
{
return new WebInspector.CPUProfileView(this);
},
canSaveToFile: function()
{
return true;
},
saveToFile: function()
{
var fileOutputStream = new WebInspector.FileOutputStream();
function getCPUProfileCallback(error, profile)
{
if (error) {
fileOutputStream.close();
return;
}
if (!profile.head) {
fileOutputStream.close();
return;
}
fileOutputStream.write(JSON.stringify(profile), fileOutputStream.close.bind(fileOutputStream));
}
function onOpen()
{
ProfilerAgent.getCPUProfile(this.uid, getCPUProfileCallback.bind(this));
}
this._fileName = this._fileName || "CPU-" + new Date().toISO8601Compact() + this._profileType.fileExtension();
fileOutputStream.open(this._fileName, onOpen.bind(this));
},
loadFromFile: function(file)
{
this.title = file.name;
this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
this.sidebarElement.wait = true;
var fileReader = new WebInspector.ChunkedFileReader(file, 10000000, this);
fileReader.start(this);
},
__proto__: WebInspector.ProfileHeader.prototype
}
;
WebInspector.CSSSelectorDataGridNode = function(profileView, data)
{
WebInspector.DataGridNode.call(this, data, false);
this._profileView = profileView;
}
WebInspector.CSSSelectorDataGridNode.prototype = {
get data()
{
var data = {};
data.selector = this._data.selector;
data.matches = this._data.matchCount;
if (this._profileView.showTimeAsPercent.get())
data.time = Number(this._data.timePercent).toFixed(1) + "%";
else
data.time = Number.secondsToString(this._data.time / 1000, true);
return data;
},
get rawData()
{
return this._data;
},
createCell: function(columnIdentifier)
{
var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
if (columnIdentifier === "selector" && cell.firstChild) {
cell.firstChild.title = this.rawData.selector;
return cell;
}
if (columnIdentifier !== "source")
return cell;
cell.removeChildren();
if (this.rawData.url) {
var wrapperDiv = cell.createChild("div");
wrapperDiv.appendChild(WebInspector.linkifyResourceAsNode(this.rawData.url, this.rawData.lineNumber));
}
return cell;
},
__proto__: WebInspector.DataGridNode.prototype
}
WebInspector.CSSSelectorProfileView = function(profile)
{
WebInspector.View.call(this);
this.element.addStyleClass("profile-view");
this.showTimeAsPercent = WebInspector.settings.createSetting("selectorProfilerShowTimeAsPercent", true);
var columns = [
{id: "selector", title: WebInspector.UIString("Selector"), width: "550px", sortable: true},
{id: "source", title: WebInspector.UIString("Source"), width: "100px", sortable: true},
{id: "time", title: WebInspector.UIString("Total"), width: "72px", sort: WebInspector.DataGrid.Order.Descending, sortable: true},
{id: "matches", title: WebInspector.UIString("Matches"), width: "72px", sortable: true}
];
this.dataGrid = new WebInspector.DataGrid(columns);
this.dataGrid.element.addStyleClass("selector-profile-view");
this.dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortProfile, this);
this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
this.dataGrid.show(this.element);
this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item");
this.percentButton.addEventListener("click", this._percentClicked, this);
this.profile = profile;
this._createProfileNodes();
this._sortProfile();
this._updatePercentButton();
}
WebInspector.CSSSelectorProfileView.prototype = {
get statusBarItems()
{
return [this.percentButton.element];
},
get profile()
{
return this._profile;
},
set profile(profile)
{
this._profile = profile;
},
_createProfileNodes: function()
{
var data = this.profile.data;
if (!data) {
return;
}
this.profile.children = [];
for (var i = 0; i < data.length; ++i) {
data[i].timePercent = data[i].time * 100 / this.profile.totalTime;
var node = new WebInspector.CSSSelectorDataGridNode(this, data[i]);
this.profile.children.push(node);
}
},
rebuildGridItems: function()
{
this.dataGrid.rootNode().removeChildren();
var children = this.profile.children;
var count = children.length;
for (var index = 0; index < count; ++index)
this.dataGrid.rootNode().appendChild(children[index]);
},
refreshData: function()
{
var child = this.dataGrid.rootNode().children[0];
while (child) {
child.refresh();
child = child.traverseNextNode(false, null, true);
}
},
refreshShowAsPercents: function()
{
this._updatePercentButton();
this.refreshData();
},
_percentClicked: function(event)
{
this.showTimeAsPercent.set(!this.showTimeAsPercent.get());
this.refreshShowAsPercents();
},
_updatePercentButton: function()
{
if (this.showTimeAsPercent.get()) {
this.percentButton.title = WebInspector.UIString("Show absolute times.");
this.percentButton.toggled = true;
} else {
this.percentButton.title = WebInspector.UIString("Show times as percentages.");
this.percentButton.toggled = false;
}
},
_sortProfile: function()
{
var sortAscending = this.dataGrid.isSortOrderAscending();
var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier();
function selectorComparator(a, b)
{
var result = b.rawData.selector.compareTo(a.rawData.selector);
return sortAscending ? -result : result;
}
function sourceComparator(a, b)
{
var aRawData = a.rawData;
var bRawData = b.rawData;
var result = bRawData.url.compareTo(aRawData.url);
if (!result)
result = bRawData.lineNumber - aRawData.lineNumber;
return sortAscending ? -result : result;
}
function timeComparator(a, b)
{
const result = b.rawData.time - a.rawData.time;
return sortAscending ? -result : result;
}
function matchesComparator(a, b)
{
const result = b.rawData.matchCount - a.rawData.matchCount;
return sortAscending ? -result : result;
}
var comparator;
switch (sortColumnIdentifier) {
case "time":
comparator = timeComparator;
break;
case "matches":
comparator = matchesComparator;
break;
case "selector":
comparator = selectorComparator;
break;
case "source":
comparator = sourceComparator;
break;
}
this.profile.children.sort(comparator);
this.rebuildGridItems();
},
_mouseDownInDataGrid: function(event)
{
if (event.detail < 2)
return;
var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
if (!cell)
return;
if (cell.hasStyleClass("time-column"))
this.showTimeAsPercent.set(!this.showTimeAsPercent.get());
else
return;
this.refreshShowAsPercents();
event.consume(true);
},
__proto__: WebInspector.View.prototype
}
WebInspector.CSSSelectorProfileType = function()
{
WebInspector.ProfileType.call(this, WebInspector.CSSSelectorProfileType.TypeId, WebInspector.UIString("Collect CSS Selector Profile"));
this._recording = false;
this._profileUid = 1;
WebInspector.CSSSelectorProfileType.instance = this;
}
WebInspector.CSSSelectorProfileType.TypeId = "SELECTOR";
WebInspector.CSSSelectorProfileType.prototype = {
get buttonTooltip()
{
return this._recording ? WebInspector.UIString("Stop CSS selector profiling.") : WebInspector.UIString("Start CSS selector profiling.");
},
buttonClicked: function()
{
if (this._recording) {
this._stopRecordingProfile();
return false;
} else {
this._startRecordingProfile();
return true;
}
},
get treeItemTitle()
{
return WebInspector.UIString("CSS SELECTOR PROFILES");
},
get description()
{
return WebInspector.UIString("CSS selector profiles show how long the selector matching has taken in total and how many times a certain selector has matched DOM elements. The results are approximate due to matching algorithm optimizations.");
},
reset: function()
{
this._profileUid = 1;
},
setRecordingProfile: function(isProfiling)
{
this._recording = isProfiling;
},
_startRecordingProfile: function()
{
this._recording = true;
CSSAgent.startSelectorProfiler();
},
_stopRecordingProfile: function()
{
function callback(error, profile)
{
if (error)
return;
var uid = this._profileUid++;
var title = WebInspector.UIString("Profile %d", uid) + String.sprintf(" (%s)", Number.secondsToString(profile.totalTime / 1000));
this.addProfile(new WebInspector.CSSProfileHeader(this, title, uid, profile));
}
this._recording = false;
CSSAgent.stopSelectorProfiler(callback.bind(this));
},
createTemporaryProfile: function(title)
{
title = title || WebInspector.UIString("Recording\u2026");
return new WebInspector.CSSProfileHeader(this, title);
},
__proto__: WebInspector.ProfileType.prototype
}
WebInspector.CSSProfileHeader = function(type, title, uid, protocolData)
{
WebInspector.ProfileHeader.call(this, type, title, uid);
this._protocolData = protocolData;
}
WebInspector.CSSProfileHeader.prototype = {
createSidebarTreeElement: function()
{
return new WebInspector.ProfileSidebarTreeElement(this, this.title, "profile-sidebar-tree-item");
},
createView: function(profilesPanel)
{
var profile = (this._protocolData);
return new WebInspector.CSSSelectorProfileView(profile);
},
__proto__: WebInspector.ProfileHeader.prototype
}
;
WebInspector.FlameChart = function(cpuProfileView)
{
WebInspector.View.call(this);
this.registerRequiredCSS("flameChart.css");
this.element.className = "fill";
this.element.id = "cpu-flame-chart";
this._overviewContainer = this.element.createChild("div", "overview-container");
this._overviewGrid = new WebInspector.OverviewGrid("flame-chart");
this._overviewContainer.appendChild(this._overviewGrid.element);
this._overviewCalculator = new WebInspector.FlameChart.OverviewCalculator();
this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
this._overviewCanvas = this._overviewContainer.createChild("canvas");
this._chartContainer = this.element.createChild("div", "chart-container");
this._timelineGrid = new WebInspector.TimelineGrid();
this._chartContainer.appendChild(this._timelineGrid.element);
this._calculator = new WebInspector.FlameChart.Calculator();
this._canvas = this._chartContainer.createChild("canvas");
WebInspector.installDragHandle(this._canvas, this._startCanvasDragging.bind(this), this._canvasDragging.bind(this), this._endCanvasDragging.bind(this), "col-resize");
this._cpuProfileView = cpuProfileView;
this._windowLeft = 0.0;
this._windowRight = 1.0;
this._barHeight = 15;
this._minWidth = 1;
this._paddingLeft = 15;
this._canvas.addEventListener("mousewheel", this._onMouseWheel.bind(this), false);
this.element.addEventListener("click", this._onClick.bind(this), false);
this._popoverHelper = new WebInspector.PopoverHelper(this._chartContainer, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
this._popoverHelper.setTimeout(250);
this._linkifier = new WebInspector.Linkifier();
this._highlightedNodeIndex = -1;
if (!WebInspector.FlameChart._colorGenerator)
WebInspector.FlameChart._colorGenerator = new WebInspector.FlameChart.ColorGenerator();
}
WebInspector.FlameChart.Calculator = function()
{
}
WebInspector.FlameChart.Calculator.prototype = {
_updateBoundaries: function(flameChart)
{
this._minimumBoundaries = flameChart._windowLeft * flameChart._timelineData.totalTime;
this._maximumBoundaries = flameChart._windowRight * flameChart._timelineData.totalTime;
this.paddingLeft = flameChart._paddingLeft;
this._width = flameChart._canvas.width - this.paddingLeft;
this._timeToPixel = this._width / this.boundarySpan();
},
computePosition: function(time)
{
return (time - this._minimumBoundaries) * this._timeToPixel + this.paddingLeft;
},
formatTime: function(value)
{
return Number.secondsToString((value + this._minimumBoundaries) / 1000);
},
maximumBoundary: function()
{
return this._maximumBoundaries;
},
minimumBoundary: function()
{
return this._minimumBoundaries;
},
zeroTime: function()
{
return 0;
},
boundarySpan: function()
{
return this._maximumBoundaries - this._minimumBoundaries;
}
}
WebInspector.FlameChart.OverviewCalculator = function()
{
}
WebInspector.FlameChart.OverviewCalculator.prototype = {
_updateBoundaries: function(flameChart)
{
this._minimumBoundaries = 0;
this._maximumBoundaries = flameChart._timelineData.totalTime;
this._xScaleFactor = flameChart._canvas.width / flameChart._timelineData.totalTime;
},
computePosition: function(time)
{
return (time - this._minimumBoundaries) * this._xScaleFactor;
},
formatTime: function(value)
{
return Number.secondsToString((value + this._minimumBoundaries) / 1000);
},
maximumBoundary: function()
{
return this._maximumBoundaries;
},
minimumBoundary: function()
{
return this._minimumBoundaries;
},
zeroTime: function()
{
return this._minimumBoundaries;
},
boundarySpan: function()
{
return this._maximumBoundaries - this._minimumBoundaries;
}
}
WebInspector.FlameChart.Events = {
SelectedNode: "SelectedNode"
}
WebInspector.FlameChart.ColorGenerator = function()
{
this._colorPairs = {};
this._currentColorIndex = 0;
}
WebInspector.FlameChart.ColorGenerator.prototype = {
_colorPairForID: function(id)
{
var colorPairs = this._colorPairs;
var colorPair = colorPairs[id];
if (!colorPair) {
var currentColorIndex = ++this._currentColorIndex;
var hue = (currentColorIndex * 5 + 11 * (currentColorIndex % 2)) % 360;
colorPairs[id] = colorPair = {highlighted: "hsla(" + hue + ", 100%, 33%, 0.7)", normal: "hsla(" + hue + ", 100%, 66%, 0.7)"};
}
return colorPair;
}
}
WebInspector.FlameChart.Entry = function(colorPair, depth, duration, startTime, node)
{
this.colorPair = colorPair;
this.depth = depth;
this.duration = duration;
this.startTime = startTime;
this.node = node;
this.selfTime = 0;
}
WebInspector.FlameChart.prototype = {
selectRange: function(timeLeft, timeRight)
{
this._overviewGrid.setWindow(timeLeft / this._totalTime, timeRight / this._totalTime);
},
_onWindowChanged: function(event)
{
this._hidePopover();
this._scheduleUpdate();
},
_startCanvasDragging: function(event)
{
if (!this._timelineData)
return false;
this._isDragging = true;
this._dragStartPoint = event.pageX;
this._dragStartWindowLeft = this._windowLeft;
this._dragStartWindowRight = this._windowRight;
this._hidePopover();
return true;
},
_canvasDragging: function(event)
{
var pixelShift = this._dragStartPoint - event.pageX;
var windowShift = pixelShift / this._totalPixels;
var windowLeft = Math.max(0, this._dragStartWindowLeft + windowShift);
if (windowLeft === this._windowLeft)
return;
windowShift = windowLeft - this._dragStartWindowLeft;
var windowRight = Math.min(1, this._dragStartWindowRight + windowShift);
if (windowRight === this._windowRight)
return;
windowShift = windowRight - this._dragStartWindowRight;
this._overviewGrid.setWindow(this._dragStartWindowLeft + windowShift, this._dragStartWindowRight + windowShift);
},
_endCanvasDragging: function()
{
this._isDragging = false;
},
_calculateTimelineData: function()
{
if (this._cpuProfileView.samples)
return this._calculateTimelineDataForSamples();
if (this._timelineData)
return this._timelineData;
if (!this._cpuProfileView.profileHead)
return null;
var index = 0;
var entries = [];
function appendReversedArray(toArray, fromArray)
{
for (var i = fromArray.length - 1; i >= 0; --i)
toArray.push(fromArray[i]);
}
var stack = [];
appendReversedArray(stack, this._cpuProfileView.profileHead.children);
var levelOffsets = ([0]);
var levelExitIndexes = ([0]);
var colorGenerator = WebInspector.FlameChart._colorGenerator;
while (stack.length) {
var level = levelOffsets.length - 1;
var node = stack.pop();
var offset = levelOffsets[level];
var colorPair = colorGenerator._colorPairForID(node.functionName + ":" + node.url + ":" + node.lineNumber);
entries.push(new WebInspector.FlameChart.Entry(colorPair, level, node.totalTime, offset, node));
++index;
levelOffsets[level] += node.totalTime;
if (node.children.length) {
levelExitIndexes.push(stack.length);
levelOffsets.push(offset + node.selfTime / 2);
appendReversedArray(stack, node.children);
}
while (stack.length === levelExitIndexes[levelExitIndexes.length - 1]) {
levelOffsets.pop();
levelExitIndexes.pop();
}
}
this._timelineData = {
entries: entries,
totalTime: this._cpuProfileView.profileHead.totalTime,
}
return this._timelineData;
},
_calculateTimelineDataForSamples: function()
{
if (this._timelineData)
return this._timelineData;
if (!this._cpuProfileView.profileHead)
return null;
var samples = this._cpuProfileView.samples;
var idToNode = this._cpuProfileView._idToNode;
var samplesCount = samples.length;
var index = 0;
var entries = ([]);
var openIntervals = [];
var stackTrace = [];
var colorGenerator = WebInspector.FlameChart._colorGenerator;
for (var sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++) {
var node = idToNode[samples[sampleIndex]];
stackTrace.length = 0;
while (node) {
stackTrace.push(node);
node = node.parent;
}
stackTrace.pop();
var depth = 0;
node = stackTrace.pop();
var intervalIndex;
while (node && depth < openIntervals.length && node === openIntervals[depth].node) {
intervalIndex = openIntervals[depth].index;
entries[intervalIndex].duration += 1;
node = stackTrace.pop();
++depth;
}
if (depth < openIntervals.length)
openIntervals.length = depth;
if (!node) {
entries[intervalIndex].selfTime += 1;
continue;
}
while (node) {
var colorPair = colorGenerator._colorPairForID(node.functionName + ":" + node.url + ":" + node.lineNumber);
entries.push(new WebInspector.FlameChart.Entry(colorPair, depth, 1, sampleIndex, node));
openIntervals.push({node: node, index: index});
++index;
node = stackTrace.pop();
++depth;
}
entries[entries.length - 1].selfTime += 1;
}
this._timelineData = {
entries: entries,
totalTime: samplesCount,
};
return this._timelineData;
},
_getPopoverAnchor: function(element, event)
{
if (this._isDragging)
return null;
var nodeIndex = this._coordinatesToNodeIndex(event.offsetX, event.offsetY);
this._highlightedNodeIndex = nodeIndex;
this.update();
if (nodeIndex === -1)
return null;
var anchorBox = new AnchorBox();
this._entryToAnchorBox(this._timelineData.entries[nodeIndex], anchorBox);
anchorBox.x += event.pageX - event.offsetX;
anchorBox.y += event.pageY - event.offsetY;
return anchorBox;
},
_showPopover: function(anchor, popover)
{
if (this._isDragging)
return;
var entry = this._timelineData.entries[this._highlightedNodeIndex];
var node = entry.node;
if (!node)
return;
var contentHelper = new WebInspector.PopoverContentHelper(node.functionName);
if (this._cpuProfileView.samples) {
contentHelper.appendTextRow(WebInspector.UIString("Self time"), Number.secondsToString(entry.selfTime / 1000, true));
contentHelper.appendTextRow(WebInspector.UIString("Total time"), Number.secondsToString(entry.duration / 1000, true));
}
contentHelper.appendTextRow(WebInspector.UIString("Aggregated self time"), Number.secondsToString(node.selfTime / 1000, true));
contentHelper.appendTextRow(WebInspector.UIString("Aggregated total time"), Number.secondsToString(node.totalTime / 1000, true));
if (node.numberOfCalls)
contentHelper.appendTextRow(WebInspector.UIString("Number of calls"), node.numberOfCalls);
if (node.url) {
var link = this._linkifier.linkifyLocation(node.url, node.lineNumber);
contentHelper.appendElementRow("Location", link);
}
popover.show(contentHelper._contentTable, anchor);
},
_hidePopover: function()
{
this._popoverHelper.hidePopover();
this._linkifier.reset();
},
_onClick: function(e)
{
if (this._highlightedNodeIndex === -1)
return;
var node = this._timelineData.entries[this._highlightedNodeIndex].node;
this.dispatchEventToListeners(WebInspector.FlameChart.Events.SelectedNode, node);
},
_onMouseWheel: function(e)
{
var zoomFactor = (e.wheelDelta > 0) ? 0.9 : 1.1;
var windowPoint = (this._pixelWindowLeft + e.offsetX) / this._totalPixels;
var overviewReferencePoint = Math.floor(windowPoint * this._pixelWindowWidth);
this._overviewGrid.zoom(zoomFactor, overviewReferencePoint);
this._hidePopover();
},
_coordinatesToNodeIndex: function(x, y)
{
var timelineData = this._timelineData;
if (!timelineData)
return -1;
var timelineEntries = timelineData.entries;
var cursorTime = (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTime;
var cursorLevel = Math.floor((this._canvas.height - y) / this._barHeight);
for (var i = 0; i < timelineEntries.length; ++i) {
if (cursorTime < timelineEntries[i].startTime)
return -1;
if (cursorTime < (timelineEntries[i].startTime + timelineEntries[i].duration)
&& cursorLevel === timelineEntries[i].depth)
return i;
}
return -1;
},
onResize: function()
{
this._updateOverviewCanvas = true;
this._hidePopover();
this._scheduleUpdate();
},
_drawOverviewCanvas: function(width, height)
{
this._overviewCanvas.width = width;
this._overviewCanvas.height = height;
if (!this._timelineData)
return;
var timelineEntries = this._timelineData.entries;
var drawData = new Uint8Array(width);
var scaleFactor = width / this._totalTime;
for (var nodeIndex = 0; nodeIndex < timelineEntries.length; ++nodeIndex) {
var entry = timelineEntries[nodeIndex];
var start = Math.floor(entry.startTime * scaleFactor);
var finish = Math.floor((entry.startTime + entry.duration) * scaleFactor);
for (var x = start; x < finish; ++x)
drawData[x] = Math.max(drawData[x], entry.depth + 1);
}
var context = this._overviewCanvas.getContext("2d");
var yScaleFactor = 2;
context.lineWidth = 0.5;
context.strokeStyle = "rgba(20,0,0,0.8)";
context.fillStyle="rgba(214,225,254, 0.8)";
context.moveTo(0, height - 1);
for (var x = 0; x < width; ++x)
context.lineTo(x, height - drawData[x] * yScaleFactor - 1);
context.lineTo(width - 1, height - 1);
context.lineTo(0, height - 1);
context.fill();
context.stroke();
context.closePath();
},
_entryToAnchorBox: function(entry, anchorBox)
{
anchorBox.x = Math.floor(entry.startTime * this._timeToPixel) - this._pixelWindowLeft + this._paddingLeft;
anchorBox.y = this._canvas.height - (entry.depth + 1) * this._barHeight;
anchorBox.width = Math.floor(entry.duration * this._timeToPixel);
anchorBox.height = this._barHeight;
if (anchorBox.x < 0) {
anchorBox.width += anchorBox.x;
anchorBox.x = 0;
}
anchorBox.width = Number.constrain(anchorBox.width, 0, this._canvas.width - anchorBox.x);
},
draw: function(width, height)
{
var timelineData = this._calculateTimelineData();
if (!timelineData)
return;
var timelineEntries = timelineData.entries;
this._canvas.height = height;
this._canvas.width = width;
var barHeight = this._barHeight;
var context = this._canvas.getContext("2d");
var textPaddingLeft = 2;
context.font = (barHeight - 3) + "px sans-serif";
context.textBaseline = "top";
this._dotsWidth = context.measureText("\u2026").width;
var visibleTimeLeft = this._timeWindowLeft - this._paddingLeftTime;
var anchorBox = new AnchorBox();
for (var i = 0; i < timelineEntries.length; ++i) {
var entry = timelineEntries[i];
var startTime = entry.startTime;
if (startTime > this._timeWindowRight)
break;
if ((startTime + entry.duration) < visibleTimeLeft)
continue;
this._entryToAnchorBox(entry, anchorBox);
if (anchorBox.width < this._minWidth)
continue;
var colorPair = entry.colorPair;
var color;
if (this._highlightedNodeIndex === i)
color = colorPair.highlighted;
else
color = colorPair.normal;
context.beginPath();
context.rect(anchorBox.x, anchorBox.y, anchorBox.width - 1, anchorBox.height - 1);
context.fillStyle = color;
context.fill();
var xText = Math.max(0, anchorBox.x);
var widthText = anchorBox.width - textPaddingLeft + anchorBox.x - xText;
var title = this._prepareTitle(context, entry.node.functionName, widthText);
if (title) {
context.fillStyle = "#333";
context.fillText(title, xText + textPaddingLeft, anchorBox.y - 1);
}
}
},
_prepareTitle: function(context, title, maxSize)
{
if (maxSize < this._dotsWidth)
return null;
var titleWidth = context.measureText(title).width;
if (maxSize > titleWidth)
return title;
maxSize -= this._dotsWidth;
var dotRegExp=/[\.\$]/g;
var match = dotRegExp.exec(title);
if (!match) {
var visiblePartSize = maxSize / titleWidth;
var newTextLength = Math.floor(title.length * visiblePartSize) + 1;
var minTextLength = 4;
if (newTextLength < minTextLength)
return null;
var substring;
do {
--newTextLength;
substring = title.substring(0, newTextLength);
} while (context.measureText(substring).width > maxSize);
return title.substring(0, newTextLength) + "\u2026";
}
while (match) {
var substring = title.substring(match.index + 1);
var width = context.measureText(substring).width;
if (maxSize > width)
return "\u2026" + substring;
match = dotRegExp.exec(title);
}
},
_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;
this._totalTime = this._timelineData.totalTime;
this._timeWindowLeft = this._windowLeft * this._totalTime;
this._timeWindowRight = this._windowRight * this._totalTime;
this._pixelWindowWidth = this._chartContainer.clientWidth;
this._totalPixels = Math.floor(this._pixelWindowWidth / this._windowWidth);
this._pixelWindowLeft = Math.floor(this._totalPixels * this._windowLeft);
this._pixelWindowRight = Math.floor(this._totalPixels * this._windowRight);
this._timeToPixel = this._totalPixels / this._totalTime;
this._pixelToTime = this._totalTime / this._totalPixels;
this._paddingLeftTime = this._paddingLeft / this._timeToPixel;
},
update: function()
{
this._updateTimerId = 0;
if (!this._timelineData)
this._calculateTimelineData();
if (!this._timelineData)
return;
this._updateBoundaries();
this.draw(this._chartContainer.clientWidth, this._chartContainer.clientHeight);
this._calculator._updateBoundaries(this);
this._overviewCalculator._updateBoundaries(this);
this._timelineGrid.element.style.width = this.element.clientWidth;
this._timelineGrid.updateDividers(this._calculator);
this._overviewGrid.updateDividers(this._overviewCalculator);
if (this._updateOverviewCanvas) {
this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight);
this._updateOverviewCanvas = false;
}
},
__proto__: WebInspector.View.prototype
};
;
WebInspector.HeapSnapshotArraySlice = function(array, start, end)
{
this._array = array;
this._start = start;
this.length = end - start;
}
WebInspector.HeapSnapshotArraySlice.prototype = {
item: function(index)
{
return this._array[this._start + index];
},
slice: function(start, end)
{
if (typeof end === "undefined")
end = this.length;
return this._array.subarray(this._start + start, this._start + end);
}
}
WebInspector.HeapSnapshotEdge = function(snapshot, edges, edgeIndex)
{
this._snapshot = snapshot;
this._edges = edges;
this.edgeIndex = edgeIndex || 0;
}
WebInspector.HeapSnapshotEdge.prototype = {
clone: function()
{
return new WebInspector.HeapSnapshotEdge(this._snapshot, this._edges, this.edgeIndex);
},
hasStringName: function()
{
throw new Error("Not implemented");
},
name: function()
{
throw new Error("Not implemented");
},
node: function()
{
return this._snapshot.createNode(this.nodeIndex());
},
nodeIndex: function()
{
return this._edges.item(this.edgeIndex + this._snapshot._edgeToNodeOffset);
},
rawEdges: function()
{
return this._edges;
},
toString: function()
{
return "HeapSnapshotEdge: " + this.name();
},
type: function()
{
return this._snapshot._edgeTypes[this._type()];
},
serialize: function()
{
var node = this.node();
return {
name: this.name(),
node: node.serialize(),
nodeIndex: this.nodeIndex(),
type: this.type(),
distance: node.distance()
};
},
_type: function()
{
return this._edges.item(this.edgeIndex + this._snapshot._edgeTypeOffset);
}
};
WebInspector.HeapSnapshotEdgeIterator = function(edge)
{
this.edge = edge;
}
WebInspector.HeapSnapshotEdgeIterator.prototype = {
rewind: function()
{
this.edge.edgeIndex = 0;
},
hasNext: function()
{
return this.edge.edgeIndex < this.edge._edges.length;
},
index: function()
{
return this.edge.edgeIndex;
},
setIndex: function(newIndex)
{
this.edge.edgeIndex = newIndex;
},
item: function()
{
return this.edge;
},
next: function()
{
this.edge.edgeIndex += this.edge._snapshot._edgeFieldsCount;
}
};
WebInspector.HeapSnapshotRetainerEdge = function(snapshot, retainedNodeIndex, retainerIndex)
{
this._snapshot = snapshot;
this._retainedNodeIndex = retainedNodeIndex;
var retainedNodeOrdinal = retainedNodeIndex / snapshot._nodeFieldCount;
this._firstRetainer = snapshot._firstRetainerIndex[retainedNodeOrdinal];
this._retainersCount = snapshot._firstRetainerIndex[retainedNodeOrdinal + 1] - this._firstRetainer;
this.setRetainerIndex(retainerIndex);
}
WebInspector.HeapSnapshotRetainerEdge.prototype = {
clone: function()
{
return new WebInspector.HeapSnapshotRetainerEdge(this._snapshot, this._retainedNodeIndex, this.retainerIndex());
},
hasStringName: function()
{
return this._edge().hasStringName();
},
name: function()
{
return this._edge().name();
},
node: function()
{
return this._node();
},
nodeIndex: function()
{
return this._nodeIndex;
},
retainerIndex: function()
{
return this._retainerIndex;
},
setRetainerIndex: function(newIndex)
{
if (newIndex !== this._retainerIndex) {
this._retainerIndex = newIndex;
this.edgeIndex = newIndex;
}
},
set edgeIndex(edgeIndex)
{
var retainerIndex = this._firstRetainer + edgeIndex;
this._globalEdgeIndex = this._snapshot._retainingEdges[retainerIndex];
this._nodeIndex = this._snapshot._retainingNodes[retainerIndex];
delete this._edgeInstance;
delete this._nodeInstance;
},
_node: function()
{
if (!this._nodeInstance)
this._nodeInstance = this._snapshot.createNode(this._nodeIndex);
return this._nodeInstance;
},
_edge: function()
{
if (!this._edgeInstance) {
var edgeIndex = this._globalEdgeIndex - this._node()._edgeIndexesStart();
this._edgeInstance = this._snapshot.createEdge(this._node().rawEdges(), edgeIndex);
}
return this._edgeInstance;
},
toString: function()
{
return this._edge().toString();
},
serialize: function()
{
var node = this.node();
return {
name: this.name(),
node: node.serialize(),
nodeIndex: this.nodeIndex(),
type: this.type(),
distance: node.distance()
};
},
type: function()
{
return this._edge().type();
}
}
WebInspector.HeapSnapshotRetainerEdgeIterator = function(retainer)
{
this.retainer = retainer;
}
WebInspector.HeapSnapshotRetainerEdgeIterator.prototype = {
rewind: function()
{
this.retainer.setRetainerIndex(0);
},
hasNext: function()
{
return this.retainer.retainerIndex() < this.retainer._retainersCount;
},
index: function()
{
return this.retainer.retainerIndex();
},
setIndex: function(newIndex)
{
this.retainer.setRetainerIndex(newIndex);
},
item: function()
{
return this.retainer;
},
next: function()
{
this.retainer.setRetainerIndex(this.retainer.retainerIndex() + 1);
}
};
WebInspector.HeapSnapshotNode = function(snapshot, nodeIndex)
{
this._snapshot = snapshot;
this._firstNodeIndex = nodeIndex;
this.nodeIndex = nodeIndex;
}
WebInspector.HeapSnapshotNode.prototype = {
distance: function()
{
return this._snapshot._nodeDistances[this.nodeIndex / this._snapshot._nodeFieldCount];
},
className: function()
{
throw new Error("Not implemented");
},
classIndex: function()
{
throw new Error("Not implemented");
},
dominatorIndex: function()
{
var nodeFieldCount = this._snapshot._nodeFieldCount;
return this._snapshot._dominatorsTree[this.nodeIndex / this._snapshot._nodeFieldCount] * nodeFieldCount;
},
edges: function()
{
return new WebInspector.HeapSnapshotEdgeIterator(this._snapshot.createEdge(this.rawEdges(), 0));
},
edgesCount: function()
{
return (this._edgeIndexesEnd() - this._edgeIndexesStart()) / this._snapshot._edgeFieldsCount;
},
id: function()
{
throw new Error("Not implemented");
},
isRoot: function()
{
return this.nodeIndex === this._snapshot._rootNodeIndex;
},
name: function()
{
return this._snapshot._strings[this._name()];
},
rawEdges: function()
{
return new WebInspector.HeapSnapshotArraySlice(this._snapshot._containmentEdges, this._edgeIndexesStart(), this._edgeIndexesEnd());
},
retainedSize: function()
{
var snapshot = this._snapshot;
return snapshot._nodes[this.nodeIndex + snapshot._nodeRetainedSizeOffset];
},
retainers: function()
{
return new WebInspector.HeapSnapshotRetainerEdgeIterator(this._snapshot.createRetainingEdge(this.nodeIndex, 0));
},
selfSize: function()
{
var snapshot = this._snapshot;
return snapshot._nodes[this.nodeIndex + snapshot._nodeSelfSizeOffset];
},
type: function()
{
return this._snapshot._nodeTypes[this._type()];
},
serialize: function()
{
return {
id: this.id(),
name: this.name(),
distance: this.distance(),
nodeIndex: this.nodeIndex,
retainedSize: this.retainedSize(),
selfSize: this.selfSize(),
type: this.type(),
};
},
_name: function()
{
var snapshot = this._snapshot;
return snapshot._nodes[this.nodeIndex + snapshot._nodeNameOffset];
},
_edgeIndexesStart: function()
{
return this._snapshot._firstEdgeIndexes[this._ordinal()];
},
_edgeIndexesEnd: function()
{
return this._snapshot._firstEdgeIndexes[this._ordinal() + 1];
},
_ordinal: function()
{
return this.nodeIndex / this._snapshot._nodeFieldCount;
},
_nextNodeIndex: function()
{
return this.nodeIndex + this._snapshot._nodeFieldCount;
},
_type: function()
{
var snapshot = this._snapshot;
return snapshot._nodes[this.nodeIndex + snapshot._nodeTypeOffset];
}
};
WebInspector.HeapSnapshotNodeIterator = function(node)
{
this.node = node;
this._nodesLength = node._snapshot._nodes.length;
}
WebInspector.HeapSnapshotNodeIterator.prototype = {
rewind: function()
{
this.node.nodeIndex = this.node._firstNodeIndex;
},
hasNext: function()
{
return this.node.nodeIndex < this._nodesLength;
},
index: function()
{
return this.node.nodeIndex;
},
setIndex: function(newIndex)
{
this.node.nodeIndex = newIndex;
},
item: function()
{
return this.node;
},
next: function()
{
this.node.nodeIndex = this.node._nextNodeIndex();
}
}
WebInspector.HeapSnapshot = function(profile)
{
this.uid = profile.snapshot.uid;
this._nodes = profile.nodes;
this._containmentEdges = profile.edges;
this._metaNode = profile.snapshot.meta;
this._strings = profile.strings;
this._rootNodeIndex = 0;
if (profile.snapshot.root_index)
this._rootNodeIndex = profile.snapshot.root_index;
this._snapshotDiffs = {};
this._aggregatesForDiff = null;
this._init();
}
function HeapSnapshotMetainfo()
{
this.node_fields = [];
this.node_types = [];
this.edge_fields = [];
this.edge_types = [];
this.type_strings = {};
this.fields = [];
this.types = [];
}
function HeapSnapshotHeader()
{
this.title = "";
this.uid = 0;
this.meta = new HeapSnapshotMetainfo();
this.node_count = 0;
this.edge_count = 0;
}
WebInspector.HeapSnapshot.prototype = {
_init: function()
{
var meta = this._metaNode;
this._nodeTypeOffset = meta.node_fields.indexOf("type");
this._nodeNameOffset = meta.node_fields.indexOf("name");
this._nodeIdOffset = meta.node_fields.indexOf("id");
this._nodeSelfSizeOffset = meta.node_fields.indexOf("self_size");
this._nodeEdgeCountOffset = meta.node_fields.indexOf("edge_count");
this._nodeFieldCount = meta.node_fields.length;
this._nodeTypes = meta.node_types[this._nodeTypeOffset];
this._nodeHiddenType = this._nodeTypes.indexOf("hidden");
this._nodeObjectType = this._nodeTypes.indexOf("object");
this._nodeNativeType = this._nodeTypes.indexOf("native");
this._nodeCodeType = this._nodeTypes.indexOf("code");
this._nodeSyntheticType = this._nodeTypes.indexOf("synthetic");
this._edgeFieldsCount = meta.edge_fields.length;
this._edgeTypeOffset = meta.edge_fields.indexOf("type");
this._edgeNameOffset = meta.edge_fields.indexOf("name_or_index");
this._edgeToNodeOffset = meta.edge_fields.indexOf("to_node");
this._edgeTypes = meta.edge_types[this._edgeTypeOffset];
this._edgeTypes.push("invisible");
this._edgeElementType = this._edgeTypes.indexOf("element");
this._edgeHiddenType = this._edgeTypes.indexOf("hidden");
this._edgeInternalType = this._edgeTypes.indexOf("internal");
this._edgeShortcutType = this._edgeTypes.indexOf("shortcut");
this._edgeWeakType = this._edgeTypes.indexOf("weak");
this._edgeInvisibleType = this._edgeTypes.indexOf("invisible");
this.nodeCount = this._nodes.length / this._nodeFieldCount;
this._edgeCount = this._containmentEdges.length / this._edgeFieldsCount;
this._buildEdgeIndexes();
this._markInvisibleEdges();
this._buildRetainers();
this._calculateFlags();
this._calculateDistances();
var result = this._buildPostOrderIndex();
this._dominatorsTree = this._buildDominatorTree(result.postOrderIndex2NodeOrdinal, result.nodeOrdinal2PostOrderIndex);
this._calculateRetainedSizes(result.postOrderIndex2NodeOrdinal);
this._buildDominatedNodes();
},
_buildEdgeIndexes: function()
{
var nodes = this._nodes;
var nodeCount = this.nodeCount;
var firstEdgeIndexes = this._firstEdgeIndexes = new Uint32Array(nodeCount + 1);
var nodeFieldCount = this._nodeFieldCount;
var edgeFieldsCount = this._edgeFieldsCount;
var nodeEdgeCountOffset = this._nodeEdgeCountOffset;
firstEdgeIndexes[nodeCount] = this._containmentEdges.length;
for (var nodeOrdinal = 0, edgeIndex = 0; nodeOrdinal < nodeCount; ++nodeOrdinal) {
firstEdgeIndexes[nodeOrdinal] = edgeIndex;
edgeIndex += nodes[nodeOrdinal * nodeFieldCount + nodeEdgeCountOffset] * edgeFieldsCount;
}
},
_buildRetainers: function()
{
var retainingNodes = this._retainingNodes = new Uint32Array(this._edgeCount);
var retainingEdges = this._retainingEdges = new Uint32Array(this._edgeCount);
var firstRetainerIndex = this._firstRetainerIndex = new Uint32Array(this.nodeCount + 1);
var containmentEdges = this._containmentEdges;
var edgeFieldsCount = this._edgeFieldsCount;
var nodeFieldCount = this._nodeFieldCount;
var edgeToNodeOffset = this._edgeToNodeOffset;
var nodes = this._nodes;
var firstEdgeIndexes = this._firstEdgeIndexes;
var nodeCount = this.nodeCount;
for (var toNodeFieldIndex = edgeToNodeOffset, l = containmentEdges.length; toNodeFieldIndex < l; toNodeFieldIndex += edgeFieldsCount) {
var toNodeIndex = containmentEdges[toNodeFieldIndex];
if (toNodeIndex % nodeFieldCount)
throw new Error("Invalid toNodeIndex " + toNodeIndex);
++firstRetainerIndex[toNodeIndex / nodeFieldCount];
}
for (var i = 0, firstUnusedRetainerSlot = 0; i < nodeCount; i++) {
var retainersCount = firstRetainerIndex[i];
firstRetainerIndex[i] = firstUnusedRetainerSlot;
retainingNodes[firstUnusedRetainerSlot] = retainersCount;
firstUnusedRetainerSlot += retainersCount;
}
firstRetainerIndex[nodeCount] = retainingNodes.length;
var nextNodeFirstEdgeIndex = firstEdgeIndexes[0];
for (var srcNodeOrdinal = 0; srcNodeOrdinal < nodeCount; ++srcNodeOrdinal) {
var firstEdgeIndex = nextNodeFirstEdgeIndex;
nextNodeFirstEdgeIndex = firstEdgeIndexes[srcNodeOrdinal + 1];
var srcNodeIndex = srcNodeOrdinal * nodeFieldCount;
for (var edgeIndex = firstEdgeIndex; edgeIndex < nextNodeFirstEdgeIndex; edgeIndex += edgeFieldsCount) {
var toNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
if (toNodeIndex % nodeFieldCount)
throw new Error("Invalid toNodeIndex " + toNodeIndex);
var firstRetainerSlotIndex = firstRetainerIndex[toNodeIndex / nodeFieldCount];
var nextUnusedRetainerSlotIndex = firstRetainerSlotIndex + (--retainingNodes[firstRetainerSlotIndex]);
retainingNodes[nextUnusedRetainerSlotIndex] = srcNodeIndex;
retainingEdges[nextUnusedRetainerSlotIndex] = edgeIndex;
}
}
},
createNode: function(nodeIndex)
{
throw new Error("Not implemented");
},
createEdge: function(edges, edgeIndex)
{
throw new Error("Not implemented");
},
createRetainingEdge: function(retainedNodeIndex, retainerIndex)
{
throw new Error("Not implemented");
},
dispose: function()
{
delete this._nodes;
delete this._strings;
delete this._retainingEdges;
delete this._retainingNodes;
delete this._firstRetainerIndex;
if (this._aggregates) {
delete this._aggregates;
delete this._aggregatesSortedFlags;
}
delete this._dominatedNodes;
delete this._firstDominatedNodeIndex;
delete this._nodeDistances;
delete this._dominatorsTree;
},
_allNodes: function()
{
return new WebInspector.HeapSnapshotNodeIterator(this.rootNode());
},
rootNode: function()
{
return this.createNode(this._rootNodeIndex);
},
get rootNodeIndex()
{
return this._rootNodeIndex;
},
get totalSize()
{
return this.rootNode().retainedSize();
},
_getDominatedIndex: function(nodeIndex)
{
if (nodeIndex % this._nodeFieldCount)
throw new Error("Invalid nodeIndex: " + nodeIndex);
return this._firstDominatedNodeIndex[nodeIndex / this._nodeFieldCount];
},
_dominatedNodesOfNode: function(node)
{
var dominatedIndexFrom = this._getDominatedIndex(node.nodeIndex);
var dominatedIndexTo = this._getDominatedIndex(node._nextNodeIndex());
return new WebInspector.HeapSnapshotArraySlice(this._dominatedNodes, dominatedIndexFrom, dominatedIndexTo);
},
aggregates: function(sortedIndexes, key, filterString)
{
if (!this._aggregates) {
this._aggregates = {};
this._aggregatesSortedFlags = {};
}
var aggregatesByClassName = this._aggregates[key];
if (aggregatesByClassName) {
if (sortedIndexes && !this._aggregatesSortedFlags[key]) {
this._sortAggregateIndexes(aggregatesByClassName);
this._aggregatesSortedFlags[key] = sortedIndexes;
}
return aggregatesByClassName;
}
var filter;
if (filterString)
filter = this._parseFilter(filterString);
var aggregates = this._buildAggregates(filter);
this._calculateClassesRetainedSize(aggregates.aggregatesByClassIndex, filter);
aggregatesByClassName = aggregates.aggregatesByClassName;
if (sortedIndexes)
this._sortAggregateIndexes(aggregatesByClassName);
this._aggregatesSortedFlags[key] = sortedIndexes;
this._aggregates[key] = aggregatesByClassName;
return aggregatesByClassName;
},
aggregatesForDiff: function()
{
if (this._aggregatesForDiff)
return this._aggregatesForDiff;
var aggregatesByClassName = this.aggregates(true, "allObjects");
this._aggregatesForDiff = {};
var node = this.createNode();
for (var className in aggregatesByClassName) {
var aggregate = aggregatesByClassName[className];
var indexes = aggregate.idxs;
var ids = new Array(indexes.length);
var selfSizes = new Array(indexes.length);
for (var i = 0; i < indexes.length; i++) {
node.nodeIndex = indexes[i];
ids[i] = node.id();
selfSizes[i] = node.selfSize();
}
this._aggregatesForDiff[className] = {
indexes: indexes,
ids: ids,
selfSizes: selfSizes
};
}
return this._aggregatesForDiff;
},
distanceForUserRoot: function(node)
{
return 1;
},
_calculateDistances: function()
{
var nodeFieldCount = this._nodeFieldCount;
var distances = new Uint32Array(this.nodeCount);
var nodesToVisit = new Uint32Array(this.nodeCount);
var nodesToVisitLength = 0;
for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
var node = iter.edge.node();
var distance = this.distanceForUserRoot(node);
if (distance !== -1) {
nodesToVisit[nodesToVisitLength++] = node.nodeIndex;
distances[node.nodeIndex / nodeFieldCount] = distance;
}
}
this._bfs(nodesToVisit, nodesToVisitLength, distances);
nodesToVisitLength = 0;
nodesToVisit[nodesToVisitLength++] = this._rootNodeIndex;
distances[this._rootNodeIndex / nodeFieldCount] = 1;
this._bfs(nodesToVisit, nodesToVisitLength, distances);
this._nodeDistances = distances;
},
_bfs: function(nodesToVisit, nodesToVisitLength, distances)
{
var edgeFieldsCount = this._edgeFieldsCount;
var nodeFieldCount = this._nodeFieldCount;
var containmentEdges = this._containmentEdges;
var firstEdgeIndexes = this._firstEdgeIndexes;
var edgeToNodeOffset = this._edgeToNodeOffset;
var edgeTypeOffset = this._edgeTypeOffset;
var nodes = this._nodes;
var nodeCount = this.nodeCount;
var containmentEdgesLength = containmentEdges.length;
var edgeWeakType = this._edgeWeakType;
var index = 0;
while (index < nodesToVisitLength) {
var nodeIndex = nodesToVisit[index++];
var nodeOrdinal = nodeIndex / nodeFieldCount;
var distance = distances[nodeOrdinal] + 1;
var firstEdgeIndex = firstEdgeIndexes[nodeOrdinal];
var edgesEnd = firstEdgeIndexes[nodeOrdinal + 1];
for (var edgeIndex = firstEdgeIndex; edgeIndex < edgesEnd; edgeIndex += edgeFieldsCount) {
var edgeType = containmentEdges[edgeIndex + edgeTypeOffset];
if (edgeType == edgeWeakType)
continue;
var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
var childNodeOrdinal = childNodeIndex / nodeFieldCount;
if (distances[childNodeOrdinal])
continue;
distances[childNodeOrdinal] = distance;
nodesToVisit[nodesToVisitLength++] = childNodeIndex;
}
}
if (nodesToVisitLength > nodeCount)
throw new Error("BFS failed. Nodes to visit (" + nodesToVisitLength + ") is more than nodes count (" + nodeCount + ")");
},
_buildAggregates: function(filter)
{
var aggregates = {};
var aggregatesByClassName = {};
var classIndexes = [];
var nodes = this._nodes;
var mapAndFlag = this.userObjectsMapAndFlag();
var flags = mapAndFlag ? mapAndFlag.map : null;
var flag = mapAndFlag ? mapAndFlag.flag : 0;
var nodesLength = nodes.length;
var nodeNativeType = this._nodeNativeType;
var nodeFieldCount = this._nodeFieldCount;
var selfSizeOffset = this._nodeSelfSizeOffset;
var nodeTypeOffset = this._nodeTypeOffset;
var node = this.rootNode();
var nodeDistances = this._nodeDistances;
for (var nodeIndex = 0; nodeIndex < nodesLength; nodeIndex += nodeFieldCount) {
var nodeOrdinal = nodeIndex / nodeFieldCount;
if (flags && !(flags[nodeOrdinal] & flag))
continue;
node.nodeIndex = nodeIndex;
if (filter && !filter(node))
continue;
var selfSize = nodes[nodeIndex + selfSizeOffset];
if (!selfSize && nodes[nodeIndex + nodeTypeOffset] !== nodeNativeType)
continue;
var classIndex = node.classIndex();
if (!(classIndex in aggregates)) {
var nodeType = node.type();
var nameMatters = nodeType === "object" || nodeType === "native";
var value = {
count: 1,
distance: nodeDistances[nodeOrdinal],
self: selfSize,
maxRet: 0,
type: nodeType,
name: nameMatters ? node.name() : null,
idxs: [nodeIndex]
};
aggregates[classIndex] = value;
classIndexes.push(classIndex);
aggregatesByClassName[node.className()] = value;
} else {
var clss = aggregates[classIndex];
clss.distance = Math.min(clss.distance, nodeDistances[nodeOrdinal]);
++clss.count;
clss.self += selfSize;
clss.idxs.push(nodeIndex);
}
}
for (var i = 0, l = classIndexes.length; i < l; ++i) {
var classIndex = classIndexes[i];
aggregates[classIndex].idxs = aggregates[classIndex].idxs.slice();
}
return {aggregatesByClassName: aggregatesByClassName, aggregatesByClassIndex: aggregates};
},
_calculateClassesRetainedSize: function(aggregates, filter)
{
var rootNodeIndex = this._rootNodeIndex;
var node = this.createNode(rootNodeIndex);
var list = [rootNodeIndex];
var sizes = [-1];
var classes = [];
var seenClassNameIndexes = {};
var nodeFieldCount = this._nodeFieldCount;
var nodeTypeOffset = this._nodeTypeOffset;
var nodeNativeType = this._nodeNativeType;
var dominatedNodes = this._dominatedNodes;
var nodes = this._nodes;
var mapAndFlag = this.userObjectsMapAndFlag();
var flags = mapAndFlag ? mapAndFlag.map : null;
var flag = mapAndFlag ? mapAndFlag.flag : 0;
var firstDominatedNodeIndex = this._firstDominatedNodeIndex;
while (list.length) {
var nodeIndex = list.pop();
node.nodeIndex = nodeIndex;
var classIndex = node.classIndex();
var seen = !!seenClassNameIndexes[classIndex];
var nodeOrdinal = nodeIndex / nodeFieldCount;
var dominatedIndexFrom = firstDominatedNodeIndex[nodeOrdinal];
var dominatedIndexTo = firstDominatedNodeIndex[nodeOrdinal + 1];
if (!seen &&
(!flags || (flags[nodeOrdinal] & flag)) &&
(!filter || filter(node)) &&
(node.selfSize() || nodes[nodeIndex + nodeTypeOffset] === nodeNativeType)
) {
aggregates[classIndex].maxRet += node.retainedSize();
if (dominatedIndexFrom !== dominatedIndexTo) {
seenClassNameIndexes[classIndex] = true;
sizes.push(list.length);
classes.push(classIndex);
}
}
for (var i = dominatedIndexFrom; i < dominatedIndexTo; i++)
list.push(dominatedNodes[i]);
var l = list.length;
while (sizes[sizes.length - 1] === l) {
sizes.pop();
classIndex = classes.pop();
seenClassNameIndexes[classIndex] = false;
}
}
},
_sortAggregateIndexes: function(aggregates)
{
var nodeA = this.createNode();
var nodeB = this.createNode();
for (var clss in aggregates)
aggregates[clss].idxs.sort(
function(idxA, idxB) {
nodeA.nodeIndex = idxA;
nodeB.nodeIndex = idxB;
return nodeA.id() < nodeB.id() ? -1 : 1;
});
},
_buildPostOrderIndex: function()
{
var nodeFieldCount = this._nodeFieldCount;
var nodes = this._nodes;
var nodeCount = this.nodeCount;
var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount;
var edgeFieldsCount = this._edgeFieldsCount;
var edgeTypeOffset = this._edgeTypeOffset;
var edgeToNodeOffset = this._edgeToNodeOffset;
var edgeShortcutType = this._edgeShortcutType;
var firstEdgeIndexes = this._firstEdgeIndexes;
var containmentEdges = this._containmentEdges;
var containmentEdgesLength = this._containmentEdges.length;
var mapAndFlag = this.userObjectsMapAndFlag();
var flags = mapAndFlag ? mapAndFlag.map : null;
var flag = mapAndFlag ? mapAndFlag.flag : 0;
var nodesToVisit = new Uint32Array(nodeCount);
var postOrderIndex2NodeOrdinal = new Uint32Array(nodeCount);
var nodeOrdinal2PostOrderIndex = new Uint32Array(nodeCount);
var painted = new Uint8Array(nodeCount);
var nodesToVisitLength = 0;
var postOrderIndex = 0;
var grey = 1;
var black = 2;
nodesToVisit[nodesToVisitLength++] = rootNodeOrdinal;
painted[rootNodeOrdinal] = grey;
while (nodesToVisitLength) {
var nodeOrdinal = nodesToVisit[nodesToVisitLength - 1];
if (painted[nodeOrdinal] === grey) {
painted[nodeOrdinal] = black;
var nodeFlag = !flags || (flags[nodeOrdinal] & flag);
var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
if (nodeOrdinal !== rootNodeOrdinal && containmentEdges[edgeIndex + edgeTypeOffset] === edgeShortcutType)
continue;
var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
var childNodeOrdinal = childNodeIndex / nodeFieldCount;
var childNodeFlag = !flags || (flags[childNodeOrdinal] & flag);
if (nodeOrdinal !== rootNodeOrdinal && childNodeFlag && !nodeFlag)
continue;
if (!painted[childNodeOrdinal]) {
painted[childNodeOrdinal] = grey;
nodesToVisit[nodesToVisitLength++] = childNodeOrdinal;
}
}
} else {
nodeOrdinal2PostOrderIndex[nodeOrdinal] = postOrderIndex;
postOrderIndex2NodeOrdinal[postOrderIndex++] = nodeOrdinal;
--nodesToVisitLength;
}
}
if (postOrderIndex !== nodeCount) {
var dumpNode = this.rootNode();
for (var i = 0; i < nodeCount; ++i) {
if (painted[i] !== black) {
dumpNode.nodeIndex = i * nodeFieldCount;
console.log(JSON.stringify(dumpNode.serialize()));
var retainers = dumpNode.retainers();
while (retainers) {
console.log("edgeName: " + retainers.item().name() + " nodeClassName: " + retainers.item().node().className());
retainers = retainers.item().node().retainers();
}
}
}
throw new Error("Postordering failed. " + (nodeCount - postOrderIndex) + " hanging nodes");
}
return {postOrderIndex2NodeOrdinal: postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex: nodeOrdinal2PostOrderIndex};
},
_buildDominatorTree: function(postOrderIndex2NodeOrdinal, nodeOrdinal2PostOrderIndex)
{
var nodeFieldCount = this._nodeFieldCount;
var nodes = this._nodes;
var firstRetainerIndex = this._firstRetainerIndex;
var retainingNodes = this._retainingNodes;
var retainingEdges = this._retainingEdges;
var edgeFieldsCount = this._edgeFieldsCount;
var edgeTypeOffset = this._edgeTypeOffset;
var edgeToNodeOffset = this._edgeToNodeOffset;
var edgeShortcutType = this._edgeShortcutType;
var firstEdgeIndexes = this._firstEdgeIndexes;
var containmentEdges = this._containmentEdges;
var containmentEdgesLength = this._containmentEdges.length;
var rootNodeIndex = this._rootNodeIndex;
var mapAndFlag = this.userObjectsMapAndFlag();
var flags = mapAndFlag ? mapAndFlag.map : null;
var flag = mapAndFlag ? mapAndFlag.flag : 0;
var nodesCount = postOrderIndex2NodeOrdinal.length;
var rootPostOrderedIndex = nodesCount - 1;
var noEntry = nodesCount;
var dominators = new Uint32Array(nodesCount);
for (var i = 0; i < rootPostOrderedIndex; ++i)
dominators[i] = noEntry;
dominators[rootPostOrderedIndex] = rootPostOrderedIndex;
var affected = new Uint8Array(nodesCount);
var nodeOrdinal;
{
nodeOrdinal = this._rootNodeIndex / nodeFieldCount;
var beginEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal] + edgeToNodeOffset;
var endEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal + 1];
for (var toNodeFieldIndex = beginEdgeToNodeFieldIndex;
toNodeFieldIndex < endEdgeToNodeFieldIndex;
toNodeFieldIndex += edgeFieldsCount) {
var childNodeOrdinal = containmentEdges[toNodeFieldIndex] / nodeFieldCount;
affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1;
}
}
var changed = true;
while (changed) {
changed = false;
for (var postOrderIndex = rootPostOrderedIndex - 1; postOrderIndex >= 0; --postOrderIndex) {
if (affected[postOrderIndex] === 0)
continue;
affected[postOrderIndex] = 0;
if (dominators[postOrderIndex] === rootPostOrderedIndex)
continue;
nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
var nodeFlag = !flags || (flags[nodeOrdinal] & flag);
var newDominatorIndex = noEntry;
var beginRetainerIndex = firstRetainerIndex[nodeOrdinal];
var endRetainerIndex = firstRetainerIndex[nodeOrdinal + 1];
for (var retainerIndex = beginRetainerIndex; retainerIndex < endRetainerIndex; ++retainerIndex) {
var retainerEdgeIndex = retainingEdges[retainerIndex];
var retainerEdgeType = containmentEdges[retainerEdgeIndex + edgeTypeOffset];
var retainerNodeIndex = retainingNodes[retainerIndex];
if (retainerNodeIndex !== rootNodeIndex && retainerEdgeType === edgeShortcutType)
continue;
var retainerNodeOrdinal = retainerNodeIndex / nodeFieldCount;
var retainerNodeFlag = !flags || (flags[retainerNodeOrdinal] & flag);
if (retainerNodeIndex !== rootNodeIndex && nodeFlag && !retainerNodeFlag)
continue;
var retanerPostOrderIndex = nodeOrdinal2PostOrderIndex[retainerNodeOrdinal];
if (dominators[retanerPostOrderIndex] !== noEntry) {
if (newDominatorIndex === noEntry)
newDominatorIndex = retanerPostOrderIndex;
else {
while (retanerPostOrderIndex !== newDominatorIndex) {
while (retanerPostOrderIndex < newDominatorIndex)
retanerPostOrderIndex = dominators[retanerPostOrderIndex];
while (newDominatorIndex < retanerPostOrderIndex)
newDominatorIndex = dominators[newDominatorIndex];
}
}
if (newDominatorIndex === rootPostOrderedIndex)
break;
}
}
if (newDominatorIndex !== noEntry && dominators[postOrderIndex] !== newDominatorIndex) {
dominators[postOrderIndex] = newDominatorIndex;
changed = true;
nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
beginEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal] + edgeToNodeOffset;
endEdgeToNodeFieldIndex = firstEdgeIndexes[nodeOrdinal + 1];
for (var toNodeFieldIndex = beginEdgeToNodeFieldIndex;
toNodeFieldIndex < endEdgeToNodeFieldIndex;
toNodeFieldIndex += edgeFieldsCount) {
var childNodeOrdinal = containmentEdges[toNodeFieldIndex] / nodeFieldCount;
affected[nodeOrdinal2PostOrderIndex[childNodeOrdinal]] = 1;
}
}
}
}
var dominatorsTree = new Uint32Array(nodesCount);
for (var postOrderIndex = 0, l = dominators.length; postOrderIndex < l; ++postOrderIndex) {
nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
dominatorsTree[nodeOrdinal] = postOrderIndex2NodeOrdinal[dominators[postOrderIndex]];
}
return dominatorsTree;
},
_calculateRetainedSizes: function(postOrderIndex2NodeOrdinal)
{
var nodeCount = this.nodeCount;
var nodes = this._nodes;
var nodeSelfSizeOffset = this._nodeSelfSizeOffset;
var nodeFieldCount = this._nodeFieldCount;
var dominatorsTree = this._dominatorsTree;
var nodeRetainedSizeOffset = this._nodeRetainedSizeOffset = this._nodeEdgeCountOffset;
delete this._nodeEdgeCountOffset;
for (var nodeIndex = 0, l = nodes.length; nodeIndex < l; nodeIndex += nodeFieldCount)
nodes[nodeIndex + nodeRetainedSizeOffset] = nodes[nodeIndex + nodeSelfSizeOffset];
for (var postOrderIndex = 0; postOrderIndex < nodeCount - 1; ++postOrderIndex) {
var nodeOrdinal = postOrderIndex2NodeOrdinal[postOrderIndex];
var nodeIndex = nodeOrdinal * nodeFieldCount;
var dominatorIndex = dominatorsTree[nodeOrdinal] * nodeFieldCount;
nodes[dominatorIndex + nodeRetainedSizeOffset] += nodes[nodeIndex + nodeRetainedSizeOffset];
}
},
_buildDominatedNodes: function()
{
var indexArray = this._firstDominatedNodeIndex = new Uint32Array(this.nodeCount + 1);
var dominatedNodes = this._dominatedNodes = new Uint32Array(this.nodeCount - 1);
var nodeFieldCount = this._nodeFieldCount;
var dominatorsTree = this._dominatorsTree;
var fromNodeOrdinal = 0;
var toNodeOrdinal = this.nodeCount;
var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount;
if (rootNodeOrdinal === fromNodeOrdinal)
fromNodeOrdinal = 1;
else if (rootNodeOrdinal === toNodeOrdinal - 1)
toNodeOrdinal = toNodeOrdinal - 1;
else
throw new Error("Root node is expected to be either first or last");
for (var nodeOrdinal = fromNodeOrdinal; nodeOrdinal < toNodeOrdinal; ++nodeOrdinal)
++indexArray[dominatorsTree[nodeOrdinal]];
var firstDominatedNodeIndex = 0;
for (var i = 0, l = this.nodeCount; i < l; ++i) {
var dominatedCount = dominatedNodes[firstDominatedNodeIndex] = indexArray[i];
indexArray[i] = firstDominatedNodeIndex;
firstDominatedNodeIndex += dominatedCount;
}
indexArray[this.nodeCount] = dominatedNodes.length;
for (var nodeOrdinal = fromNodeOrdinal; nodeOrdinal < toNodeOrdinal; ++nodeOrdinal) {
var dominatorOrdinal = dominatorsTree[nodeOrdinal];
var dominatedRefIndex = indexArray[dominatorOrdinal];
dominatedRefIndex += (--dominatedNodes[dominatedRefIndex]);
dominatedNodes[dominatedRefIndex] = nodeOrdinal * nodeFieldCount;
}
},
_markInvisibleEdges: function()
{
throw new Error("Not implemented");
},
_numbersComparator: function(a, b)
{
return a < b ? -1 : (a > b ? 1 : 0);
},
_calculateFlags: function()
{
throw new Error("Not implemented");
},
userObjectsMapAndFlag: function()
{
throw new Error("Not implemented");
},
calculateSnapshotDiff: function(baseSnapshotId, baseSnapshotAggregates)
{
var snapshotDiff = this._snapshotDiffs[baseSnapshotId];
if (snapshotDiff)
return snapshotDiff;
snapshotDiff = {};
var aggregates = this.aggregates(true, "allObjects");
for (var className in baseSnapshotAggregates) {
var baseAggregate = baseSnapshotAggregates[className];
var diff = this._calculateDiffForClass(baseAggregate, aggregates[className]);
if (diff)
snapshotDiff[className] = diff;
}
var emptyBaseAggregate = { ids: [], indexes: [], selfSizes: [] };
for (var className in aggregates) {
if (className in baseSnapshotAggregates)
continue;
snapshotDiff[className] = this._calculateDiffForClass(emptyBaseAggregate, aggregates[className]);
}
this._snapshotDiffs[baseSnapshotId] = snapshotDiff;
return snapshotDiff;
},
_calculateDiffForClass: function(baseAggregate, aggregate)
{
var baseIds = baseAggregate.ids;
var baseIndexes = baseAggregate.indexes;
var baseSelfSizes = baseAggregate.selfSizes;
var indexes = aggregate ? aggregate.idxs : [];
var i = 0, l = baseIds.length;
var j = 0, m = indexes.length;
var diff = { addedCount: 0,
removedCount: 0,
addedSize: 0,
removedSize: 0,
deletedIndexes: [],
addedIndexes: [] };
var nodeB = this.createNode(indexes[j]);
while (i < l && j < m) {
var nodeAId = baseIds[i];
if (nodeAId < nodeB.id()) {
diff.deletedIndexes.push(baseIndexes[i]);
diff.removedCount++;
diff.removedSize += baseSelfSizes[i];
++i;
} else if (nodeAId > nodeB.id()) {
diff.addedIndexes.push(indexes[j]);
diff.addedCount++;
diff.addedSize += nodeB.selfSize();
nodeB.nodeIndex = indexes[++j];
} else {
++i;
nodeB.nodeIndex = indexes[++j];
}
}
while (i < l) {
diff.deletedIndexes.push(baseIndexes[i]);
diff.removedCount++;
diff.removedSize += baseSelfSizes[i];
++i;
}
while (j < m) {
diff.addedIndexes.push(indexes[j]);
diff.addedCount++;
diff.addedSize += nodeB.selfSize();
nodeB.nodeIndex = indexes[++j];
}
diff.countDelta = diff.addedCount - diff.removedCount;
diff.sizeDelta = diff.addedSize - diff.removedSize;
if (!diff.addedCount && !diff.removedCount)
return null;
return diff;
},
_nodeForSnapshotObjectId: function(snapshotObjectId)
{
for (var it = this._allNodes(); it.hasNext(); it.next()) {
if (it.node.id() === snapshotObjectId)
return it.node;
}
return null;
},
nodeClassName: function(snapshotObjectId)
{
var node = this._nodeForSnapshotObjectId(snapshotObjectId);
if (node)
return node.className();
return null;
},
dominatorIdsForNode: function(snapshotObjectId)
{
var node = this._nodeForSnapshotObjectId(snapshotObjectId);
if (!node)
return null;
var result = [];
while (!node.isRoot()) {
result.push(node.id());
node.nodeIndex = node.dominatorIndex();
}
return result;
},
_parseFilter: function(filter)
{
if (!filter)
return null;
var parsedFilter = eval("(function(){return " + filter + "})()");
return parsedFilter.bind(this);
},
createEdgesProvider: function(nodeIndex, showHiddenData)
{
var node = this.createNode(nodeIndex);
var filter = this.containmentEdgesFilter(showHiddenData);
return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.edges());
},
createEdgesProviderForTest: function(nodeIndex, filter)
{
var node = this.createNode(nodeIndex);
return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.edges());
},
retainingEdgesFilter: function(showHiddenData)
{
return null;
},
containmentEdgesFilter: function(showHiddenData)
{
return null;
},
createRetainingEdgesProvider: function(nodeIndex, showHiddenData)
{
var node = this.createNode(nodeIndex);
var filter = this.retainingEdgesFilter(showHiddenData);
return new WebInspector.HeapSnapshotEdgesProvider(this, filter, node.retainers());
},
createAddedNodesProvider: function(baseSnapshotId, className)
{
var snapshotDiff = this._snapshotDiffs[baseSnapshotId];
var diffForClass = snapshotDiff[className];
return new WebInspector.HeapSnapshotNodesProvider(this, null, diffForClass.addedIndexes);
},
createDeletedNodesProvider: function(nodeIndexes)
{
return new WebInspector.HeapSnapshotNodesProvider(this, null, nodeIndexes);
},
classNodesFilter: function()
{
return null;
},
createNodesProviderForClass: function(className, aggregatesKey)
{
return new WebInspector.HeapSnapshotNodesProvider(this, this.classNodesFilter(), this.aggregates(false, aggregatesKey)[className].idxs);
},
createNodesProviderForDominator: function(nodeIndex)
{
var node = this.createNode(nodeIndex);
return new WebInspector.HeapSnapshotNodesProvider(this, null, this._dominatedNodesOfNode(node));
},
updateStaticData: function()
{
return {nodeCount: this.nodeCount, rootNodeIndex: this._rootNodeIndex, totalSize: this.totalSize, uid: this.uid};
}
};
WebInspector.HeapSnapshotFilteredOrderedIterator = function(iterator, filter, unfilteredIterationOrder)
{
this._filter = filter;
this._iterator = iterator;
this._unfilteredIterationOrder = unfilteredIterationOrder;
this._iterationOrder = null;
this._position = 0;
this._currentComparator = null;
this._sortedPrefixLength = 0;
}
WebInspector.HeapSnapshotFilteredOrderedIterator.prototype = {
_createIterationOrder: function()
{
if (this._iterationOrder)
return;
if (this._unfilteredIterationOrder && !this._filter) {
this._iterationOrder = this._unfilteredIterationOrder.slice(0);
this._unfilteredIterationOrder = null;
return;
}
this._iterationOrder = [];
var iterator = this._iterator;
if (!this._unfilteredIterationOrder && !this._filter) {
for (iterator.rewind(); iterator.hasNext(); iterator.next())
this._iterationOrder.push(iterator.index());
} else if (!this._unfilteredIterationOrder) {
for (iterator.rewind(); iterator.hasNext(); iterator.next()) {
if (this._filter(iterator.item()))
this._iterationOrder.push(iterator.index());
}
} else {
var order = this._unfilteredIterationOrder.constructor === Array ?
this._unfilteredIterationOrder : this._unfilteredIterationOrder.slice(0);
for (var i = 0, l = order.length; i < l; ++i) {
iterator.setIndex(order[i]);
if (this._filter(iterator.item()))
this._iterationOrder.push(iterator.index());
}
this._unfilteredIterationOrder = null;
}
},
rewind: function()
{
this._position = 0;
},
hasNext: function()
{
return this._position < this._iterationOrder.length;
},
isEmpty: function()
{
if (this._iterationOrder)
return !this._iterationOrder.length;
if (this._unfilteredIterationOrder && !this._filter)
return !this._unfilteredIterationOrder.length;
var iterator = this._iterator;
if (!this._unfilteredIterationOrder && !this._filter) {
iterator.rewind();
return !iterator.hasNext();
} else if (!this._unfilteredIterationOrder) {
for (iterator.rewind(); iterator.hasNext(); iterator.next())
if (this._filter(iterator.item()))
return false;
} else {
var order = this._unfilteredIterationOrder.constructor === Array ?
this._unfilteredIterationOrder : this._unfilteredIterationOrder.slice(0);
for (var i = 0, l = order.length; i < l; ++i) {
iterator.setIndex(order[i]);
if (this._filter(iterator.item()))
return false;
}
}
return true;
},
item: function()
{
this._iterator.setIndex(this._iterationOrder[this._position]);
return this._iterator.item();
},
get length()
{
this._createIterationOrder();
return this._iterationOrder.length;
},
next: function()
{
++this._position;
},
serializeItemsRange: function(begin, end)
{
this._createIterationOrder();
if (begin > end)
throw new Error("Start position > end position: " + begin + " > " + end);
if (end >= this._iterationOrder.length)
end = this._iterationOrder.length;
if (this._sortedPrefixLength < end) {
this.sort(this._currentComparator, this._sortedPrefixLength, this._iterationOrder.length - 1, end - this._sortedPrefixLength);
this._sortedPrefixLength = end;
}
this._position = begin;
var startPosition = this._position;
var count = end - begin;
var result = new Array(count);
for (var i = 0 ; i < count && this.hasNext(); ++i, this.next())
result[i] = this.item().serialize();
result.length = i;
result.totalLength = this._iterationOrder.length;
result.startPosition = startPosition;
result.endPosition = this._position;
return result;
},
sortAll: function()
{
this._createIterationOrder();
if (this._sortedPrefixLength === this._iterationOrder.length)
return;
this.sort(this._currentComparator, this._sortedPrefixLength, this._iterationOrder.length - 1, this._iterationOrder.length);
this._sortedPrefixLength = this._iterationOrder.length;
},
sortAndRewind: function(comparator)
{
this._currentComparator = comparator;
this._sortedPrefixLength = 0;
this.rewind();
}
}
WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator = function(fieldNames)
{
return {fieldName1: fieldNames[0], ascending1: fieldNames[1], fieldName2: fieldNames[2], ascending2: fieldNames[3]};
}
WebInspector.HeapSnapshotEdgesProvider = function(snapshot, filter, edgesIter)
{
this.snapshot = snapshot;
WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, edgesIter, filter);
}
WebInspector.HeapSnapshotEdgesProvider.prototype = {
sort: function(comparator, leftBound, rightBound, count)
{
var fieldName1 = comparator.fieldName1;
var fieldName2 = comparator.fieldName2;
var ascending1 = comparator.ascending1;
var ascending2 = comparator.ascending2;
var edgeA = this._iterator.item().clone();
var edgeB = edgeA.clone();
var nodeA = this.snapshot.createNode();
var nodeB = this.snapshot.createNode();
function compareEdgeFieldName(ascending, indexA, indexB)
{
edgeA.edgeIndex = indexA;
edgeB.edgeIndex = indexB;
if (edgeB.name() === "__proto__") return -1;
if (edgeA.name() === "__proto__") return 1;
var result =
edgeA.hasStringName() === edgeB.hasStringName() ?
(edgeA.name() < edgeB.name() ? -1 : (edgeA.name() > edgeB.name() ? 1 : 0)) :
(edgeA.hasStringName() ? -1 : 1);
return ascending ? result : -result;
}
function compareNodeField(fieldName, ascending, indexA, indexB)
{
edgeA.edgeIndex = indexA;
nodeA.nodeIndex = edgeA.nodeIndex();
var valueA = nodeA[fieldName]();
edgeB.edgeIndex = indexB;
nodeB.nodeIndex = edgeB.nodeIndex();
var valueB = nodeB[fieldName]();
var result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0);
return ascending ? result : -result;
}
function compareEdgeAndNode(indexA, indexB) {
var result = compareEdgeFieldName(ascending1, indexA, indexB);
if (result === 0)
result = compareNodeField(fieldName2, ascending2, indexA, indexB);
return result;
}
function compareNodeAndEdge(indexA, indexB) {
var result = compareNodeField(fieldName1, ascending1, indexA, indexB);
if (result === 0)
result = compareEdgeFieldName(ascending2, indexA, indexB);
return result;
}
function compareNodeAndNode(indexA, indexB) {
var result = compareNodeField(fieldName1, ascending1, indexA, indexB);
if (result === 0)
result = compareNodeField(fieldName2, ascending2, indexA, indexB);
return result;
}
if (fieldName1 === "!edgeName")
this._iterationOrder.sortRange(compareEdgeAndNode, leftBound, rightBound, count);
else if (fieldName2 === "!edgeName")
this._iterationOrder.sortRange(compareNodeAndEdge, leftBound, rightBound, count);
else
this._iterationOrder.sortRange(compareNodeAndNode, leftBound, rightBound, count);
},
__proto__: WebInspector.HeapSnapshotFilteredOrderedIterator.prototype
}
WebInspector.HeapSnapshotNodesProvider = function(snapshot, filter, nodeIndexes)
{
this.snapshot = snapshot;
WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, snapshot._allNodes(), filter, nodeIndexes);
}
WebInspector.HeapSnapshotNodesProvider.prototype = {
nodePosition: function(snapshotObjectId)
{
this._createIterationOrder();
if (this.isEmpty())
return -1;
this.sortAll();
var node = this.snapshot.createNode();
for (var i = 0; i < this._iterationOrder.length; i++) {
node.nodeIndex = this._iterationOrder[i];
if (node.id() === snapshotObjectId)
return i;
}
return -1;
},
sort: function(comparator, leftBound, rightBound, count)
{
var fieldName1 = comparator.fieldName1;
var fieldName2 = comparator.fieldName2;
var ascending1 = comparator.ascending1;
var ascending2 = comparator.ascending2;
var nodeA = this.snapshot.createNode();
var nodeB = this.snapshot.createNode();
function sortByNodeField(fieldName, ascending)
{
var valueOrFunctionA = nodeA[fieldName];
var valueA = typeof valueOrFunctionA !== "function" ? valueOrFunctionA : valueOrFunctionA.call(nodeA);
var valueOrFunctionB = nodeB[fieldName];
var valueB = typeof valueOrFunctionB !== "function" ? valueOrFunctionB : valueOrFunctionB.call(nodeB);
var result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0);
return ascending ? result : -result;
}
function sortByComparator(indexA, indexB) {
nodeA.nodeIndex = indexA;
nodeB.nodeIndex = indexB;
var result = sortByNodeField(fieldName1, ascending1);
if (result === 0)
result = sortByNodeField(fieldName2, ascending2);
return result;
}
this._iterationOrder.sortRange(sortByComparator, leftBound, rightBound, count);
},
__proto__: WebInspector.HeapSnapshotFilteredOrderedIterator.prototype
}
;
WebInspector.HeapSnapshotSortableDataGrid = function(columns)
{
WebInspector.DataGrid.call(this, columns);
this._recursiveSortingDepth = 0;
this._highlightedNode = null;
this._populatedAndSorted = false;
this.addEventListener("sorting complete", this._sortingComplete, this);
this.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this.sortingChanged, this);
}
WebInspector.HeapSnapshotSortableDataGrid.Events = {
ContentShown: "ContentShown"
}
WebInspector.HeapSnapshotSortableDataGrid.prototype = {
defaultPopulateCount: function()
{
return 100;
},
dispose: function()
{
var children = this.topLevelNodes();
for (var i = 0, l = children.length; i < l; ++i)
children[i].dispose();
},
wasShown: function()
{
if (this._populatedAndSorted)
this.dispatchEventToListeners(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, this);
},
_sortingComplete: function()
{
this.removeEventListener("sorting complete", this._sortingComplete, this);
this._populatedAndSorted = true;
this.dispatchEventToListeners(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, this);
},
willHide: function()
{
this._clearCurrentHighlight();
},
populateContextMenu: function(profilesPanel, contextMenu, event)
{
var td = event.target.enclosingNodeOrSelfWithNodeName("td");
if (!td)
return;
var node = td.heapSnapshotNode;
if (node instanceof WebInspector.HeapSnapshotInstanceNode || node instanceof WebInspector.HeapSnapshotObjectNode) {
function revealInDominatorsView()
{
profilesPanel.showObject(node.snapshotNodeId, "Dominators");
}
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInDominatorsView.bind(this));
} else if (node instanceof WebInspector.HeapSnapshotDominatorObjectNode) {
function revealInSummaryView()
{
profilesPanel.showObject(node.snapshotNodeId, "Summary");
}
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInSummaryView.bind(this));
}
},
resetSortingCache: function()
{
delete this._lastSortColumnIdentifier;
delete this._lastSortAscending;
},
topLevelNodes: function()
{
return this.rootNode().children;
},
highlightObjectByHeapSnapshotId: function(heapSnapshotObjectId)
{
},
highlightNode: function(node)
{
var prevNode = this._highlightedNode;
this._clearCurrentHighlight();
this._highlightedNode = node;
this._highlightedNode.element.addStyleClass("highlighted-row");
if (node === prevNode) {
var element = node.element;
var parent = element.parentElement;
var nextSibling = element.nextSibling;
parent.removeChild(element);
parent.insertBefore(element, nextSibling);
}
},
nodeWasDetached: function(node)
{
if (this._highlightedNode === node)
this._clearCurrentHighlight();
},
_clearCurrentHighlight: function()
{
if (!this._highlightedNode)
return
this._highlightedNode.element.removeStyleClass("highlighted-row");
this._highlightedNode = null;
},
changeNameFilter: function(filter)
{
filter = filter.toLowerCase();
var children = this.topLevelNodes();
for (var i = 0, l = children.length; i < l; ++i) {
var node = children[i];
if (node.depth === 0)
node.revealed = node._name.toLowerCase().indexOf(filter) !== -1;
}
this.updateVisibleNodes();
},
sortingChanged: function()
{
var sortAscending = this.isSortOrderAscending();
var sortColumnIdentifier = this.sortColumnIdentifier();
if (this._lastSortColumnIdentifier === sortColumnIdentifier && this._lastSortAscending === sortAscending)
return;
this._lastSortColumnIdentifier = sortColumnIdentifier;
this._lastSortAscending = sortAscending;
var sortFields = this._sortFields(sortColumnIdentifier, sortAscending);
function SortByTwoFields(nodeA, nodeB)
{
var field1 = nodeA[sortFields[0]];
var field2 = nodeB[sortFields[0]];
var result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
if (!sortFields[1])
result = -result;
if (result !== 0)
return result;
field1 = nodeA[sortFields[2]];
field2 = nodeB[sortFields[2]];
result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
if (!sortFields[3])
result = -result;
return result;
}
this._performSorting(SortByTwoFields);
},
_performSorting: function(sortFunction)
{
this.recursiveSortingEnter();
var children = this._topLevelNodes;
this.rootNode().removeChildren();
children.sort(sortFunction);
for (var i = 0, l = children.length; i < l; ++i) {
var child = children[i];
this.appendChildAfterSorting(child);
if (child.expanded)
child.sort();
}
this.updateVisibleNodes();
this.recursiveSortingLeave();
},
appendChildAfterSorting: function(child)
{
var revealed = child.revealed;
this.rootNode().appendChild(child);
child.revealed = revealed;
},
updateVisibleNodes: function()
{
},
recursiveSortingEnter: function()
{
++this._recursiveSortingDepth;
},
recursiveSortingLeave: function()
{
if (!this._recursiveSortingDepth)
return;
if (!--this._recursiveSortingDepth)
this.dispatchEventToListeners("sorting complete");
},
__proto__: WebInspector.DataGrid.prototype
}
WebInspector.HeapSnapshotViewportDataGrid = function(columns)
{
WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
this.scrollContainer.addEventListener("scroll", this._onScroll.bind(this), true);
this._topLevelNodes = [];
this._topPadding = new WebInspector.HeapSnapshotPaddingNode();
this._bottomPadding = new WebInspector.HeapSnapshotPaddingNode();
this._nodeToHighlightAfterScroll = null;
}
WebInspector.HeapSnapshotViewportDataGrid.prototype = {
topLevelNodes: function()
{
return this._topLevelNodes;
},
appendChildAfterSorting: function(child)
{
},
updateVisibleNodes: function()
{
var scrollTop = this.scrollContainer.scrollTop;
var viewPortHeight = this.scrollContainer.offsetHeight;
this._removePaddingRows();
var children = this._topLevelNodes;
var i = 0;
var topPadding = 0;
while (i < children.length) {
if (children[i].revealed) {
var newTop = topPadding + children[i].nodeHeight();
if (newTop > scrollTop)
break;
topPadding = newTop;
}
++i;
}
this.rootNode().removeChildren();
var heightToFill = viewPortHeight + (scrollTop - topPadding);
var filledHeight = 0;
while (i < children.length && filledHeight < heightToFill) {
if (children[i].revealed) {
this.rootNode().appendChild(children[i]);
filledHeight += children[i].nodeHeight();
}
++i;
}
var bottomPadding = 0;
while (i < children.length) {
bottomPadding += children[i].nodeHeight();
++i;
}
this._addPaddingRows(topPadding, bottomPadding);
},
appendTopLevelNode: function(node)
{
this._topLevelNodes.push(node);
},
removeTopLevelNodes: function()
{
this.rootNode().removeChildren();
this._topLevelNodes = [];
},
highlightNode: function(node)
{
if (this._isScrolledIntoView(node.element))
WebInspector.HeapSnapshotSortableDataGrid.prototype.highlightNode.call(this, node);
else {
node.element.scrollIntoViewIfNeeded(true);
this._nodeToHighlightAfterScroll = node;
}
},
_isScrolledIntoView: function(element)
{
var viewportTop = this.scrollContainer.scrollTop;
var viewportBottom = viewportTop + this.scrollContainer.clientHeight;
var elemTop = element.offsetTop
var elemBottom = elemTop + element.offsetHeight;
return elemBottom <= viewportBottom && elemTop >= viewportTop;
},
_addPaddingRows: function(top, bottom)
{
if (this._topPadding.element.parentNode !== this.dataTableBody)
this.dataTableBody.insertBefore(this._topPadding.element, this.dataTableBody.firstChild);
if (this._bottomPadding.element.parentNode !== this.dataTableBody)
this.dataTableBody.insertBefore(this._bottomPadding.element, this.dataTableBody.lastChild);
this._topPadding.setHeight(top);
this._bottomPadding.setHeight(bottom);
},
_removePaddingRows: function()
{
this._bottomPadding.removeFromTable();
this._topPadding.removeFromTable();
},
onResize: function()
{
WebInspector.HeapSnapshotSortableDataGrid.prototype.onResize.call(this);
this.updateVisibleNodes();
},
_onScroll: function(event)
{
this.updateVisibleNodes();
if (this._nodeToHighlightAfterScroll) {
WebInspector.HeapSnapshotSortableDataGrid.prototype.highlightNode.call(this, this._nodeToHighlightAfterScroll);
this._nodeToHighlightAfterScroll = null;
}
},
__proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype
}
WebInspector.HeapSnapshotPaddingNode = function()
{
this.element = document.createElement("tr");
this.element.addStyleClass("revealed");
}
WebInspector.HeapSnapshotPaddingNode.prototype = {
setHeight: function(height)
{
this.element.style.height = height + "px";
},
removeFromTable: function()
{
var parent = this.element.parentNode;
if (parent)
parent.removeChild(this.element);
}
}
WebInspector.HeapSnapshotContainmentDataGrid = function(columns)
{
columns = columns || [
{id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true},
{id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
{id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sortable: true, sort: WebInspector.DataGrid.Order.Descending}
];
WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
}
WebInspector.HeapSnapshotContainmentDataGrid.prototype = {
setDataSource: function(snapshot, nodeIndex)
{
this.snapshot = snapshot;
var node = new WebInspector.HeapSnapshotNode(snapshot, nodeIndex || snapshot.rootNodeIndex);
var fakeEdge = { node: node };
this.setRootNode(new WebInspector.HeapSnapshotObjectNode(this, false, fakeEdge, null));
this.rootNode().sort();
},
sortingChanged: function()
{
this.rootNode().sort();
},
__proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype
}
WebInspector.HeapSnapshotRetainmentDataGrid = function()
{
this.showRetainingEdges = true;
var columns = [
{id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true},
{id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
{id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sortable: true},
{id: "distance", title: WebInspector.UIString("Distance"), width: "80px", sortable: true, sort: WebInspector.DataGrid.Order.Ascending}
];
WebInspector.HeapSnapshotContainmentDataGrid.call(this, columns);
}
WebInspector.HeapSnapshotRetainmentDataGrid.Events = {
ExpandRetainersComplete: "ExpandRetainersComplete"
}
WebInspector.HeapSnapshotRetainmentDataGrid.prototype = {
_sortFields: function(sortColumn, sortAscending)
{
return {
object: ["_name", sortAscending, "_count", false],
count: ["_count", sortAscending, "_name", true],
shallowSize: ["_shallowSize", sortAscending, "_name", true],
retainedSize: ["_retainedSize", sortAscending, "_name", true],
distance: ["_distance", sortAscending, "_name", true]
}[sortColumn];
},
reset: function()
{
this.rootNode().removeChildren();
this.resetSortingCache();
},
setDataSource: function(snapshot, nodeIndex)
{
WebInspector.HeapSnapshotContainmentDataGrid.prototype.setDataSource.call(this, snapshot, nodeIndex);
var dataGrid = this;
var maxExpandLevels = 20;
function populateComplete()
{
this.removeEventListener(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete, populateComplete, this);
this.expand();
if (--maxExpandLevels > 0 && this.children.length > 0 && (!this._distance || this._distance > 2)) {
var retainer = this.children[0];
retainer.addEventListener(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete, populateComplete, retainer);
retainer.populate();
} else
dataGrid.dispatchEventToListeners(WebInspector.HeapSnapshotRetainmentDataGrid.Events.ExpandRetainersComplete);
}
this.rootNode().addEventListener(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete, populateComplete, this.rootNode());
},
__proto__: WebInspector.HeapSnapshotContainmentDataGrid.prototype
}
WebInspector.HeapSnapshotConstructorsDataGrid = function()
{
var columns = [
{id: "object", title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true},
{id: "distance", title: WebInspector.UIString("Distance"), width: "90px", sortable: true},
{id: "count", title: WebInspector.UIString("Objects Count"), width: "90px", sortable: true},
{id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
{id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true}
];
WebInspector.HeapSnapshotViewportDataGrid.call(this, columns);
this._profileIndex = -1;
this._topLevelNodes = [];
this._objectIdToSelect = null;
}
WebInspector.HeapSnapshotConstructorsDataGrid.prototype = {
_sortFields: function(sortColumn, sortAscending)
{
return {
object: ["_name", sortAscending, "_count", false],
distance: ["_distance", sortAscending, "_retainedSize", true],
count: ["_count", sortAscending, "_name", true],
shallowSize: ["_shallowSize", sortAscending, "_name", true],
retainedSize: ["_retainedSize", sortAscending, "_name", true]
}[sortColumn];
},
highlightObjectByHeapSnapshotId: function(id)
{
if (!this.snapshot) {
this._objectIdToSelect = id;
return;
}
function didGetClassName(className)
{
var constructorNodes = this.topLevelNodes();
for (var i = 0; i < constructorNodes.length; i++) {
var parent = constructorNodes[i];
if (parent._name === className) {
parent.revealNodeBySnapshotObjectId(parseInt(id, 10));
return;
}
}
}
this.snapshot.nodeClassName(parseInt(id, 10), didGetClassName.bind(this));
},
setDataSource: function(snapshot)
{
this.snapshot = snapshot;
if (this._profileIndex === -1)
this._populateChildren();
if (this._objectIdToSelect) {
this.highlightObjectByHeapSnapshotId(this._objectIdToSelect);
this._objectIdToSelect = null;
}
},
_aggregatesReceived: function(key, aggregates)
{
for (var constructor in aggregates)
this.appendTopLevelNode(new WebInspector.HeapSnapshotConstructorNode(this, constructor, aggregates[constructor], key));
this.sortingChanged();
},
_populateChildren: function()
{
this.dispose();
this.removeTopLevelNodes();
this.resetSortingCache();
var key = this._profileIndex === -1 ? "allObjects" : this._minNodeId + ".." + this._maxNodeId;
var filter = this._profileIndex === -1 ? null : "function(node) { var id = node.id(); return id > " + this._minNodeId + " && id <= " + this._maxNodeId + "; }";
this.snapshot.aggregates(false, key, filter, this._aggregatesReceived.bind(this, key));
},
filterSelectIndexChanged: function(profiles, profileIndex)
{
this._profileIndex = profileIndex;
delete this._maxNodeId;
delete this._minNodeId;
if (this._profileIndex !== -1) {
this._minNodeId = profileIndex > 0 ? profiles[profileIndex - 1].maxJSObjectId : 0;
this._maxNodeId = profiles[profileIndex].maxJSObjectId;
}
this._populateChildren();
},
__proto__: WebInspector.HeapSnapshotViewportDataGrid.prototype
}
WebInspector.HeapSnapshotDiffDataGrid = function()
{
var columns = [
{id: "object", title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true},
{id: "addedCount", title: WebInspector.UIString("# New"), width: "72px", sortable: true},
{id: "removedCount", title: WebInspector.UIString("# Deleted"), width: "72px", sortable: true},
{id: "countDelta", title: "# Delta", width: "64px", sortable: true},
{id: "addedSize", title: WebInspector.UIString("Alloc. Size"), width: "72px", sortable: true, sort: WebInspector.DataGrid.Order.Descending},
{id: "removedSize", title: WebInspector.UIString("Freed Size"), width: "72px", sortable: true},
{id: "sizeDelta", title: "Size Delta", width: "72px", sortable: true}
];
WebInspector.HeapSnapshotViewportDataGrid.call(this, columns);
}
WebInspector.HeapSnapshotDiffDataGrid.prototype = {
defaultPopulateCount: function()
{
return 50;
},
_sortFields: function(sortColumn, sortAscending)
{
return {
object: ["_name", sortAscending, "_count", false],
addedCount: ["_addedCount", sortAscending, "_name", true],
removedCount: ["_removedCount", sortAscending, "_name", true],
countDelta: ["_countDelta", sortAscending, "_name", true],
addedSize: ["_addedSize", sortAscending, "_name", true],
removedSize: ["_removedSize", sortAscending, "_name", true],
sizeDelta: ["_sizeDelta", sortAscending, "_name", true]
}[sortColumn];
},
setDataSource: function(snapshot)
{
this.snapshot = snapshot;
},
setBaseDataSource: function(baseSnapshot)
{
this.baseSnapshot = baseSnapshot;
this.dispose();
this.removeTopLevelNodes();
this.resetSortingCache();
if (this.baseSnapshot === this.snapshot) {
this.dispatchEventToListeners("sorting complete");
return;
}
this._populateChildren();
},
_populateChildren: function()
{
function aggregatesForDiffReceived(aggregatesForDiff)
{
this.snapshot.calculateSnapshotDiff(this.baseSnapshot.uid, aggregatesForDiff, didCalculateSnapshotDiff.bind(this));
function didCalculateSnapshotDiff(diffByClassName)
{
for (var className in diffByClassName) {
var diff = diffByClassName[className];
this.appendTopLevelNode(new WebInspector.HeapSnapshotDiffNode(this, className, diff));
}
this.sortingChanged();
}
}
this.baseSnapshot.aggregatesForDiff(aggregatesForDiffReceived.bind(this));
},
__proto__: WebInspector.HeapSnapshotViewportDataGrid.prototype
}
WebInspector.HeapSnapshotDominatorsDataGrid = function()
{
var columns = [
{id: "object", title: WebInspector.UIString("Object"), disclosure: true, sortable: true},
{id: "shallowSize", title: WebInspector.UIString("Shallow Size"), width: "120px", sortable: true},
{id: "retainedSize", title: WebInspector.UIString("Retained Size"), width: "120px", sort: WebInspector.DataGrid.Order.Descending, sortable: true}
];
WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
this._objectIdToSelect = null;
}
WebInspector.HeapSnapshotDominatorsDataGrid.prototype = {
defaultPopulateCount: function()
{
return 25;
},
setDataSource: function(snapshot)
{
this.snapshot = snapshot;
var fakeNode = { nodeIndex: this.snapshot.rootNodeIndex };
this.setRootNode(new WebInspector.HeapSnapshotDominatorObjectNode(this, fakeNode));
this.rootNode().sort();
if (this._objectIdToSelect) {
this.highlightObjectByHeapSnapshotId(this._objectIdToSelect);
this._objectIdToSelect = null;
}
},
sortingChanged: function()
{
this.rootNode().sort();
},
highlightObjectByHeapSnapshotId: function(id)
{
if (!this.snapshot) {
this._objectIdToSelect = id;
return;
}
function didGetDominators(dominatorIds)
{
if (!dominatorIds) {
WebInspector.log(WebInspector.UIString("Cannot find corresponding heap snapshot node"));
return;
}
var dominatorNode = this.rootNode();
expandNextDominator.call(this, dominatorIds, dominatorNode);
}
function expandNextDominator(dominatorIds, dominatorNode)
{
if (!dominatorNode) {
console.error("Cannot find dominator node");
return;
}
if (!dominatorIds.length) {
this.highlightNode(dominatorNode);
dominatorNode.element.scrollIntoViewIfNeeded(true);
return;
}
var snapshotObjectId = dominatorIds.pop();
dominatorNode.retrieveChildBySnapshotObjectId(snapshotObjectId, expandNextDominator.bind(this, dominatorIds));
}
this.snapshot.dominatorIdsForNode(parseInt(id, 10), didGetDominators.bind(this));
},
__proto__: WebInspector.HeapSnapshotSortableDataGrid.prototype
}
;
WebInspector.HeapSnapshotGridNode = function(tree, hasChildren)
{
WebInspector.DataGridNode.call(this, null, hasChildren);
this._dataGrid = tree;
this._instanceCount = 0;
this._savedChildren = null;
this._retrievedChildrenRanges = [];
}
WebInspector.HeapSnapshotGridNode.Events = {
PopulateComplete: "PopulateComplete"
}
WebInspector.HeapSnapshotGridNode.prototype = {
createProvider: function()
{
throw new Error("Needs implemented.");
},
_provider: function()
{
if (!this._providerObject)
this._providerObject = this.createProvider();
return this._providerObject;
},
createCell: function(columnIdentifier)
{
var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
if (this._searchMatched)
cell.addStyleClass("highlight");
return cell;
},
collapse: function()
{
WebInspector.DataGridNode.prototype.collapse.call(this);
this._dataGrid.updateVisibleNodes();
},
dispose: function()
{
if (this._provider())
this._provider().dispose();
for (var node = this.children[0]; node; node = node.traverseNextNode(true, this, true))
if (node.dispose)
node.dispose();
},
_reachableFromWindow: false,
queryObjectContent: function(callback)
{
},
wasDetached: function()
{
this._dataGrid.nodeWasDetached(this);
},
_toPercentString: function(num)
{
return num.toFixed(0) + "\u2009%";
},
childForPosition: function(nodePosition)
{
var indexOfFirsChildInRange = 0;
for (var i = 0; i < this._retrievedChildrenRanges.length; i++) {
var range = this._retrievedChildrenRanges[i];
if (range.from <= nodePosition && nodePosition < range.to) {
var childIndex = indexOfFirsChildInRange + nodePosition - range.from;
return this.children[childIndex];
}
indexOfFirsChildInRange += range.to - range.from + 1;
}
return null;
},
_createValueCell: function(columnIdentifier)
{
var cell = document.createElement("td");
cell.className = columnIdentifier + "-column";
if (this.dataGrid.snapshot.totalSize !== 0) {
var div = document.createElement("div");
var valueSpan = document.createElement("span");
valueSpan.textContent = this.data[columnIdentifier];
div.appendChild(valueSpan);
var percentColumn = columnIdentifier + "-percent";
if (percentColumn in this.data) {
var percentSpan = document.createElement("span");
percentSpan.className = "percent-column";
percentSpan.textContent = this.data[percentColumn];
div.appendChild(percentSpan);
div.addStyleClass("heap-snapshot-multiple-values");
}
cell.appendChild(div);
}
return cell;
},
populate: function(event)
{
if (this._populated)
return;
this._populated = true;
function sorted()
{
this._populateChildren();
}
this._provider().sortAndRewind(this.comparator(), sorted.bind(this));
},
expandWithoutPopulate: function(callback)
{
this._populated = true;
this.expand();
this._provider().sortAndRewind(this.comparator(), callback);
},
_populateChildren: function(fromPosition, toPosition, afterPopulate)
{
fromPosition = fromPosition || 0;
toPosition = toPosition || fromPosition + this._dataGrid.defaultPopulateCount();
var firstNotSerializedPosition = fromPosition;
function serializeNextChunk()
{
if (firstNotSerializedPosition >= toPosition)
return;
var end = Math.min(firstNotSerializedPosition + this._dataGrid.defaultPopulateCount(), toPosition);
this._provider().serializeItemsRange(firstNotSerializedPosition, end, childrenRetrieved.bind(this));
firstNotSerializedPosition = end;
}
function insertRetrievedChild(item, insertionIndex)
{
if (this._savedChildren) {
var hash = this._childHashForEntity(item);
if (hash in this._savedChildren) {
this.insertChild(this._savedChildren[hash], insertionIndex);
return;
}
}
this.insertChild(this._createChildNode(item), insertionIndex);
}
function insertShowMoreButton(from, to, insertionIndex)
{
var button = new WebInspector.ShowMoreDataGridNode(this._populateChildren.bind(this), from, to, this._dataGrid.defaultPopulateCount());
this.insertChild(button, insertionIndex);
}
function childrenRetrieved(items)
{
var itemIndex = 0;
var itemPosition = items.startPosition;
var insertionIndex = 0;
if (!this._retrievedChildrenRanges.length) {
if (items.startPosition > 0) {
this._retrievedChildrenRanges.push({from: 0, to: 0});
insertShowMoreButton.call(this, 0, items.startPosition, insertionIndex++);
}
this._retrievedChildrenRanges.push({from: items.startPosition, to: items.endPosition});
for (var i = 0, l = items.length; i < l; ++i)
insertRetrievedChild.call(this, items[i], insertionIndex++);
if (items.endPosition < items.totalLength)
insertShowMoreButton.call(this, items.endPosition, items.totalLength, insertionIndex++);
} else {
var rangeIndex = 0;
var found = false;
var range;
while (rangeIndex < this._retrievedChildrenRanges.length) {
range = this._retrievedChildrenRanges[rangeIndex];
if (range.to >= itemPosition) {
found = true;
break;
}
insertionIndex += range.to - range.from;
if (range.to < items.totalLength)
insertionIndex += 1;
++rangeIndex;
}
if (!found || items.startPosition < range.from) {
this.children[insertionIndex - 1].setEndPosition(items.startPosition);
insertShowMoreButton.call(this, items.startPosition, found ? range.from : items.totalLength, insertionIndex);
range = {from: items.startPosition, to: items.startPosition};
if (!found)
rangeIndex = this._retrievedChildrenRanges.length;
this._retrievedChildrenRanges.splice(rangeIndex, 0, range);
} else {
insertionIndex += itemPosition - range.from;
}
while (range.to < items.endPosition) {
var skipCount = range.to - itemPosition;
insertionIndex += skipCount;
itemIndex += skipCount;
itemPosition = range.to;
var nextRange = this._retrievedChildrenRanges[rangeIndex + 1];
var newEndOfRange = nextRange ? nextRange.from : items.totalLength;
if (newEndOfRange > items.endPosition)
newEndOfRange = items.endPosition;
while (itemPosition < newEndOfRange) {
insertRetrievedChild.call(this, items[itemIndex++], insertionIndex++);
++itemPosition;
}
if (nextRange && newEndOfRange === nextRange.from) {
range.to = nextRange.to;
this.removeChild(this.children[insertionIndex]);
this._retrievedChildrenRanges.splice(rangeIndex + 1, 1);
} else {
range.to = newEndOfRange;
if (newEndOfRange === items.totalLength)
this.removeChild(this.children[insertionIndex]);
else
this.children[insertionIndex].setStartPosition(items.endPosition);
}
}
}
this._instanceCount += items.length;
if (firstNotSerializedPosition < toPosition) {
serializeNextChunk.call(this);
return;
}
if (afterPopulate)
afterPopulate();
this.dispatchEventToListeners(WebInspector.HeapSnapshotGridNode.Events.PopulateComplete);
}
serializeNextChunk.call(this);
},
_saveChildren: function()
{
this._savedChildren = null;
for (var i = 0, childrenCount = this.children.length; i < childrenCount; ++i) {
var child = this.children[i];
if (!child.expanded)
continue;
if (!this._savedChildren)
this._savedChildren = {};
this._savedChildren[this._childHashForNode(child)] = child;
}
},
sort: function()
{
this._dataGrid.recursiveSortingEnter();
function afterSort()
{
this._saveChildren();
this.removeChildren();
this._retrievedChildrenRanges = [];
function afterPopulate()
{
for (var i = 0, l = this.children.length; i < l; ++i) {
var child = this.children[i];
if (child.expanded)
child.sort();
}
this._dataGrid.recursiveSortingLeave();
}
var instanceCount = this._instanceCount;
this._instanceCount = 0;
this._populateChildren(0, instanceCount, afterPopulate.bind(this));
}
this._provider().sortAndRewind(this.comparator(), afterSort.bind(this));
},
__proto__: WebInspector.DataGridNode.prototype
}
WebInspector.HeapSnapshotGenericObjectNode = function(tree, node)
{
this.snapshotNodeIndex = 0;
WebInspector.HeapSnapshotGridNode.call(this, tree, false);
if (!node)
return;
this._name = node.name;
this._displayName = node.displayName;
this._type = node.type;
this._distance = node.distance;
this._shallowSize = node.selfSize;
this._retainedSize = node.retainedSize;
this.snapshotNodeId = node.id;
this.snapshotNodeIndex = node.nodeIndex;
if (this._type === "string")
this._reachableFromWindow = true;
else if (this._type === "object" && this._name.startsWith("Window")) {
this._name = this.shortenWindowURL(this._name, false);
this._reachableFromWindow = true;
} else if (node.canBeQueried)
this._reachableFromWindow = true;
if (node.detachedDOMTreeNode)
this.detachedDOMTreeNode = true;
};
WebInspector.HeapSnapshotGenericObjectNode.prototype = {
createCell: function(columnIdentifier)
{
var cell = columnIdentifier !== "object" ? this._createValueCell(columnIdentifier) : this._createObjectCell();
if (this._searchMatched)
cell.addStyleClass("highlight");
return cell;
},
_createObjectCell: function()
{
var cell = document.createElement("td");
cell.className = "object-column";
var div = document.createElement("div");
div.className = "source-code event-properties";
div.style.overflow = "visible";
var data = this.data["object"];
if (this._prefixObjectCell)
this._prefixObjectCell(div, data);
var valueSpan = document.createElement("span");
valueSpan.className = "value console-formatted-" + data.valueStyle;
valueSpan.textContent = data.value;
div.appendChild(valueSpan);
if (this.data.displayName) {
var nameSpan = document.createElement("span");
nameSpan.className = "name console-formatted-name";
nameSpan.textContent = " " + this.data.displayName;
div.appendChild(nameSpan);
}
var idSpan = document.createElement("span");
idSpan.className = "console-formatted-id";
idSpan.textContent = " @" + data["nodeId"];
div.appendChild(idSpan);
if (this._postfixObjectCell)
this._postfixObjectCell(div, data);
cell.appendChild(div);
cell.addStyleClass("disclosure");
if (this.depth)
cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px");
cell.heapSnapshotNode = this;
return cell;
},
get data()
{
var data = this._emptyData();
var value = this._name;
var valueStyle = "object";
switch (this._type) {
case "string":
value = "\"" + value + "\"";
valueStyle = "string";
break;
case "regexp":
value = "/" + value + "/";
valueStyle = "string";
break;
case "closure":
value = "function" + (value ? " " : "") + value + "()";
valueStyle = "function";
break;
case "number":
valueStyle = "number";
break;
case "hidden":
valueStyle = "null";
break;
case "array":
if (!value)
value = "[]";
else
value += "[]";
break;
};
if (this._reachableFromWindow)
valueStyle += " highlight";
if (value === "Object")
value = "";
if (this.detachedDOMTreeNode)
valueStyle += " detached-dom-tree-node";
data["object"] = { valueStyle: valueStyle, value: value, nodeId: this.snapshotNodeId };
data["displayName"] = this._displayName;
data["distance"] = this._distance;
data["shallowSize"] = Number.withThousandsSeparator(this._shallowSize);
data["retainedSize"] = Number.withThousandsSeparator(this._retainedSize);
data["shallowSize-percent"] = this._toPercentString(this._shallowSizePercent);
data["retainedSize-percent"] = this._toPercentString(this._retainedSizePercent);
return this._enhanceData ? this._enhanceData(data) : data;
},
queryObjectContent: function(callback, objectGroupName)
{
if (this._type === "string")
callback(WebInspector.RemoteObject.fromPrimitiveValue(this._name));
else {
function formatResult(error, object)
{
if (!error && object.type)
callback(WebInspector.RemoteObject.fromPayload(object), !!error);
else
callback(WebInspector.RemoteObject.fromPrimitiveValue(WebInspector.UIString("Not available")));
}
HeapProfilerAgent.getObjectByHeapObjectId(String(this.snapshotNodeId), objectGroupName, formatResult);
}
},
get _retainedSizePercent()
{
return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0;
},
get _shallowSizePercent()
{
return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0;
},
updateHasChildren: function()
{
function isEmptyCallback(isEmpty)
{
this.hasChildren = !isEmpty;
}
this._provider().isEmpty(isEmptyCallback.bind(this));
},
shortenWindowURL: function(fullName, hasObjectId)
{
var startPos = fullName.indexOf("/");
var endPos = hasObjectId ? fullName.indexOf("@") : fullName.length;
if (startPos !== -1 && endPos !== -1) {
var fullURL = fullName.substring(startPos + 1, endPos).trimLeft();
var url = fullURL.trimURL();
if (url.length > 40)
url = url.trimMiddle(40);
return fullName.substr(0, startPos + 2) + url + fullName.substr(endPos);
} else
return fullName;
},
__proto__: WebInspector.HeapSnapshotGridNode.prototype
}
WebInspector.HeapSnapshotObjectNode = function(tree, isFromBaseSnapshot, edge, parentGridNode)
{
WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, edge.node);
this._referenceName = edge.name;
this._referenceType = edge.type;
this._distance = edge.distance;
this.showRetainingEdges = tree.showRetainingEdges;
this._isFromBaseSnapshot = isFromBaseSnapshot;
this._parentGridNode = parentGridNode;
this._cycledWithAncestorGridNode = this._findAncestorWithSameSnapshotNodeId();
if (!this._cycledWithAncestorGridNode)
this.updateHasChildren();
}
WebInspector.HeapSnapshotObjectNode.prototype = {
createProvider: function()
{
var tree = this._dataGrid;
var showHiddenData = WebInspector.settings.showHeapSnapshotObjectsHiddenProperties.get();
var snapshot = this._isFromBaseSnapshot ? tree.baseSnapshot : tree.snapshot;
if (this.showRetainingEdges)
return snapshot.createRetainingEdgesProvider(this.snapshotNodeIndex, showHiddenData);
else
return snapshot.createEdgesProvider(this.snapshotNodeIndex, showHiddenData);
},
_findAncestorWithSameSnapshotNodeId: function()
{
var ancestor = this._parentGridNode;
while (ancestor) {
if (ancestor.snapshotNodeId === this.snapshotNodeId)
return ancestor;
ancestor = ancestor._parentGridNode;
}
return null;
},
_createChildNode: function(item)
{
return new WebInspector.HeapSnapshotObjectNode(this._dataGrid, this._isFromBaseSnapshot, item, this);
},
_childHashForEntity: function(edge)
{
var prefix = this.showRetainingEdges ? edge.node.id + "#" : "";
return prefix + edge.type + "#" + edge.name;
},
_childHashForNode: function(childNode)
{
var prefix = this.showRetainingEdges ? childNode.snapshotNodeId + "#" : "";
return prefix + childNode._referenceType + "#" + childNode._referenceName;
},
comparator: function()
{
var sortAscending = this._dataGrid.isSortOrderAscending();
var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier();
var sortFields = {
object: ["!edgeName", sortAscending, "retainedSize", false],
count: ["!edgeName", true, "retainedSize", false],
shallowSize: ["selfSize", sortAscending, "!edgeName", true],
retainedSize: ["retainedSize", sortAscending, "!edgeName", true],
distance: ["distance", sortAscending, "_name", true]
}[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false];
return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
},
_emptyData: function()
{
return { count: "", addedCount: "", removedCount: "", countDelta: "", addedSize: "", removedSize: "", sizeDelta: "" };
},
_enhanceData: function(data)
{
var name = this._referenceName;
if (name === "") name = "(empty)";
var nameClass = "name";
switch (this._referenceType) {
case "context":
nameClass = "console-formatted-number";
break;
case "internal":
case "hidden":
nameClass = "console-formatted-null";
break;
case "element":
name = "[" + name + "]";
break;
}
data["object"].nameClass = nameClass;
data["object"].name = name;
data["distance"] = this._distance;
return data;
},
_prefixObjectCell: function(div, data)
{
if (this._cycledWithAncestorGridNode)
div.className += " cycled-ancessor-node";
var nameSpan = document.createElement("span");
nameSpan.className = data.nameClass;
nameSpan.textContent = data.name;
div.appendChild(nameSpan);
var separatorSpan = document.createElement("span");
separatorSpan.className = "grayed";
separatorSpan.textContent = this.showRetainingEdges ? " in " : " :: ";
div.appendChild(separatorSpan);
},
__proto__: WebInspector.HeapSnapshotGenericObjectNode.prototype
}
WebInspector.HeapSnapshotInstanceNode = function(tree, baseSnapshot, snapshot, node)
{
WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node);
this._baseSnapshotOrSnapshot = baseSnapshot || snapshot;
this._isDeletedNode = !!baseSnapshot;
this.updateHasChildren();
};
WebInspector.HeapSnapshotInstanceNode.prototype = {
createProvider: function()
{
var showHiddenData = WebInspector.settings.showHeapSnapshotObjectsHiddenProperties.get();
return this._baseSnapshotOrSnapshot.createEdgesProvider(
this.snapshotNodeIndex,
showHiddenData);
},
_createChildNode: function(item)
{
return new WebInspector.HeapSnapshotObjectNode(this._dataGrid, this._isDeletedNode, item, null);
},
_childHashForEntity: function(edge)
{
return edge.type + "#" + edge.name;
},
_childHashForNode: function(childNode)
{
return childNode._referenceType + "#" + childNode._referenceName;
},
comparator: function()
{
var sortAscending = this._dataGrid.isSortOrderAscending();
var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier();
var sortFields = {
object: ["!edgeName", sortAscending, "retainedSize", false],
distance: ["distance", sortAscending, "retainedSize", false],
count: ["!edgeName", true, "retainedSize", false],
addedSize: ["selfSize", sortAscending, "!edgeName", true],
removedSize: ["selfSize", sortAscending, "!edgeName", true],
shallowSize: ["selfSize", sortAscending, "!edgeName", true],
retainedSize: ["retainedSize", sortAscending, "!edgeName", true]
}[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false];
return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
},
_emptyData: function()
{
return {count: "", countDelta: "", sizeDelta: ""};
},
_enhanceData: function(data)
{
if (this._isDeletedNode) {
data["addedCount"] = "";
data["addedSize"] = "";
data["removedCount"] = "\u2022";
data["removedSize"] = Number.withThousandsSeparator(this._shallowSize);
} else {
data["addedCount"] = "\u2022";
data["addedSize"] = Number.withThousandsSeparator(this._shallowSize);
data["removedCount"] = "";
data["removedSize"] = "";
}
return data;
},
get isDeletedNode()
{
return this._isDeletedNode;
},
__proto__: WebInspector.HeapSnapshotGenericObjectNode.prototype
}
WebInspector.HeapSnapshotConstructorNode = function(tree, className, aggregate, aggregatesKey)
{
WebInspector.HeapSnapshotGridNode.call(this, tree, aggregate.count > 0);
this._name = className;
this._aggregatesKey = aggregatesKey;
this._distance = aggregate.distance;
this._count = aggregate.count;
this._shallowSize = aggregate.self;
this._retainedSize = aggregate.maxRet;
}
WebInspector.HeapSnapshotConstructorNode.prototype = {
createProvider: function()
{
return this._dataGrid.snapshot.createNodesProviderForClass(this._name, this._aggregatesKey)
},
revealNodeBySnapshotObjectId: function(snapshotObjectId)
{
function didExpand()
{
this._provider().nodePosition(snapshotObjectId, didGetNodePosition.bind(this));
}
function didGetNodePosition(nodePosition)
{
if (nodePosition === -1)
this.collapse();
else
this._populateChildren(nodePosition, null, didPopulateChildren.bind(this, nodePosition));
}
function didPopulateChildren(nodePosition)
{
var indexOfFirsChildInRange = 0;
for (var i = 0; i < this._retrievedChildrenRanges.length; i++) {
var range = this._retrievedChildrenRanges[i];
if (range.from <= nodePosition && nodePosition < range.to) {
var childIndex = indexOfFirsChildInRange + nodePosition - range.from;
var instanceNode = this.children[childIndex];
this._dataGrid.highlightNode(instanceNode);
return;
}
indexOfFirsChildInRange += range.to - range.from + 1;
}
}
this.expandWithoutPopulate(didExpand.bind(this));
},
createCell: function(columnIdentifier)
{
var cell = columnIdentifier !== "object" ? this._createValueCell(columnIdentifier) : WebInspector.HeapSnapshotGridNode.prototype.createCell.call(this, columnIdentifier);
if (this._searchMatched)
cell.addStyleClass("highlight");
return cell;
},
_createChildNode: function(item)
{
return new WebInspector.HeapSnapshotInstanceNode(this._dataGrid, null, this._dataGrid.snapshot, item);
},
comparator: function()
{
var sortAscending = this._dataGrid.isSortOrderAscending();
var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier();
var sortFields = {
object: ["id", sortAscending, "retainedSize", false],
distance: ["distance", true, "retainedSize", false],
count: ["id", true, "retainedSize", false],
shallowSize: ["selfSize", sortAscending, "id", true],
retainedSize: ["retainedSize", sortAscending, "id", true]
}[sortColumnIdentifier];
return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
},
_childHashForEntity: function(node)
{
return node.id;
},
_childHashForNode: function(childNode)
{
return childNode.snapshotNodeId;
},
get data()
{
var data = { object: this._name };
data["count"] = Number.withThousandsSeparator(this._count);
data["distance"] = this._distance;
data["shallowSize"] = Number.withThousandsSeparator(this._shallowSize);
data["retainedSize"] = Number.withThousandsSeparator(this._retainedSize);
data["count-percent"] = this._toPercentString(this._countPercent);
data["shallowSize-percent"] = this._toPercentString(this._shallowSizePercent);
data["retainedSize-percent"] = this._toPercentString(this._retainedSizePercent);
return data;
},
get _countPercent()
{
return this._count / this.dataGrid.snapshot.nodeCount * 100.0;
},
get _retainedSizePercent()
{
return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0;
},
get _shallowSizePercent()
{
return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0;
},
__proto__: WebInspector.HeapSnapshotGridNode.prototype
}
WebInspector.HeapSnapshotDiffNodesProvider = function(addedNodesProvider, deletedNodesProvider, addedCount, removedCount)
{
this._addedNodesProvider = addedNodesProvider;
this._deletedNodesProvider = deletedNodesProvider;
this._addedCount = addedCount;
this._removedCount = removedCount;
}
WebInspector.HeapSnapshotDiffNodesProvider.prototype = {
dispose: function()
{
this._addedNodesProvider.dispose();
this._deletedNodesProvider.dispose();
},
isEmpty: function(callback)
{
callback(false);
},
serializeItemsRange: function(beginPosition, endPosition, callback)
{
function didReceiveAllItems(items)
{
items.totalLength = this._addedCount + this._removedCount;
callback(items);
}
function didReceiveDeletedItems(addedItems, items)
{
if (!addedItems.length)
addedItems.startPosition = this._addedCount + items.startPosition;
for (var i = 0; i < items.length; i++) {
items[i].isAddedNotRemoved = false;
addedItems.push(items[i]);
}
addedItems.endPosition = this._addedCount + items.endPosition;
didReceiveAllItems.call(this, addedItems);
}
function didReceiveAddedItems(items)
{
for (var i = 0; i < items.length; i++)
items[i].isAddedNotRemoved = true;
if (items.endPosition < endPosition)
return this._deletedNodesProvider.serializeItemsRange(0, endPosition - items.endPosition, didReceiveDeletedItems.bind(this, items));
items.totalLength = this._addedCount + this._removedCount;
didReceiveAllItems.call(this, items);
}
if (beginPosition < this._addedCount)
this._addedNodesProvider.serializeItemsRange(beginPosition, endPosition, didReceiveAddedItems.bind(this));
else
this._deletedNodesProvider.serializeItemsRange(beginPosition - this._addedCount, endPosition - this._addedCount, didReceiveDeletedItems.bind(this, []));
},
sortAndRewind: function(comparator, callback)
{
function afterSort()
{
this._deletedNodesProvider.sortAndRewind(comparator, callback);
}
this._addedNodesProvider.sortAndRewind(comparator, afterSort.bind(this));
}
};
WebInspector.HeapSnapshotDiffNode = function(tree, className, diffForClass)
{
WebInspector.HeapSnapshotGridNode.call(this, tree, true);
this._name = className;
this._addedCount = diffForClass.addedCount;
this._removedCount = diffForClass.removedCount;
this._countDelta = diffForClass.countDelta;
this._addedSize = diffForClass.addedSize;
this._removedSize = diffForClass.removedSize;
this._sizeDelta = diffForClass.sizeDelta;
this._deletedIndexes = diffForClass.deletedIndexes;
}
WebInspector.HeapSnapshotDiffNode.prototype = {
createProvider: function()
{
var tree = this._dataGrid;
return new WebInspector.HeapSnapshotDiffNodesProvider(
tree.snapshot.createAddedNodesProvider(tree.baseSnapshot.uid, this._name),
tree.baseSnapshot.createDeletedNodesProvider(this._deletedIndexes),
this._addedCount,
this._removedCount);
},
_createChildNode: function(item)
{
if (item.isAddedNotRemoved)
return new WebInspector.HeapSnapshotInstanceNode(this._dataGrid, null, this._dataGrid.snapshot, item);
else
return new WebInspector.HeapSnapshotInstanceNode(this._dataGrid, this._dataGrid.baseSnapshot, null, item);
},
_childHashForEntity: function(node)
{
return node.id;
},
_childHashForNode: function(childNode)
{
return childNode.snapshotNodeId;
},
comparator: function()
{
var sortAscending = this._dataGrid.isSortOrderAscending();
var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier();
var sortFields = {
object: ["id", sortAscending, "selfSize", false],
addedCount: ["selfSize", sortAscending, "id", true],
removedCount: ["selfSize", sortAscending, "id", true],
countDelta: ["selfSize", sortAscending, "id", true],
addedSize: ["selfSize", sortAscending, "id", true],
removedSize: ["selfSize", sortAscending, "id", true],
sizeDelta: ["selfSize", sortAscending, "id", true]
}[sortColumnIdentifier];
return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
},
_signForDelta: function(delta)
{
if (delta === 0)
return "";
if (delta > 0)
return "+";
else
return "\u2212";
},
get data()
{
var data = {object: this._name};
data["addedCount"] = Number.withThousandsSeparator(this._addedCount);
data["removedCount"] = Number.withThousandsSeparator(this._removedCount);
data["countDelta"] = this._signForDelta(this._countDelta) + Number.withThousandsSeparator(Math.abs(this._countDelta));
data["addedSize"] = Number.withThousandsSeparator(this._addedSize);
data["removedSize"] = Number.withThousandsSeparator(this._removedSize);
data["sizeDelta"] = this._signForDelta(this._sizeDelta) + Number.withThousandsSeparator(Math.abs(this._sizeDelta));
return data;
},
__proto__: WebInspector.HeapSnapshotGridNode.prototype
}
WebInspector.HeapSnapshotDominatorObjectNode = function(tree, node)
{
WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node);
this.updateHasChildren();
};
WebInspector.HeapSnapshotDominatorObjectNode.prototype = {
createProvider: function()
{
return this._dataGrid.snapshot.createNodesProviderForDominator(this.snapshotNodeIndex);
},
retrieveChildBySnapshotObjectId: function(snapshotObjectId, callback)
{
function didExpand()
{
this._provider().nodePosition(snapshotObjectId, didGetNodePosition.bind(this));
}
function didGetNodePosition(nodePosition)
{
if (nodePosition === -1) {
this.collapse();
callback(null);
} else
this._populateChildren(nodePosition, null, didPopulateChildren.bind(this, nodePosition));
}
function didPopulateChildren(nodePosition)
{
var child = this.childForPosition(nodePosition);
callback(child);
}
this.hasChildren = true;
this.expandWithoutPopulate(didExpand.bind(this));
},
_createChildNode: function(item)
{
return new WebInspector.HeapSnapshotDominatorObjectNode(this._dataGrid, item);
},
_childHashForEntity: function(node)
{
return node.id;
},
_childHashForNode: function(childNode)
{
return childNode.snapshotNodeId;
},
comparator: function()
{
var sortAscending = this._dataGrid.isSortOrderAscending();
var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier();
var sortFields = {
object: ["id", sortAscending, "retainedSize", false],
shallowSize: ["selfSize", sortAscending, "id", true],
retainedSize: ["retainedSize", sortAscending, "id", true]
}[sortColumnIdentifier];
return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields);
},
_emptyData: function()
{
return {};
},
__proto__: WebInspector.HeapSnapshotGenericObjectNode.prototype
}
;
WebInspector.HeapSnapshotLoader = function()
{
this._reset();
}
WebInspector.HeapSnapshotLoader.prototype = {
dispose: function()
{
this._reset();
},
_reset: function()
{
this._json = "";
this._state = "find-snapshot-info";
this._snapshot = {};
},
close: function()
{
if (this._json)
this._parseStringsArray();
},
buildSnapshot: function(constructorName)
{
var constructor = WebInspector[constructorName];
var result = new constructor(this._snapshot);
this._reset();
return result;
},
_parseUintArray: function()
{
var index = 0;
var char0 = "0".charCodeAt(0), char9 = "9".charCodeAt(0), closingBracket = "]".charCodeAt(0);
var length = this._json.length;
while (true) {
while (index < length) {
var code = this._json.charCodeAt(index);
if (char0 <= code && code <= char9)
break;
else if (code === closingBracket) {
this._json = this._json.slice(index + 1);
return false;
}
++index;
}
if (index === length) {
this._json = "";
return true;
}
var nextNumber = 0;
var startIndex = index;
while (index < length) {
var code = this._json.charCodeAt(index);
if (char0 > code || code > char9)
break;
nextNumber *= 10;
nextNumber += (code - char0);
++index;
}
if (index === length) {
this._json = this._json.slice(startIndex);
return true;
}
this._array[this._arrayIndex++] = nextNumber;
}
},
_parseStringsArray: function()
{
var closingBracketIndex = this._json.lastIndexOf("]");
if (closingBracketIndex === -1)
throw new Error("Incomplete JSON");
this._json = this._json.slice(0, closingBracketIndex + 1);
this._snapshot.strings = JSON.parse(this._json);
},
write: function(chunk)
{
this._json += chunk;
switch (this._state) {
case "find-snapshot-info": {
var snapshotToken = "\"snapshot\"";
var snapshotTokenIndex = this._json.indexOf(snapshotToken);
if (snapshotTokenIndex === -1)
throw new Error("Snapshot token not found");
this._json = this._json.slice(snapshotTokenIndex + snapshotToken.length + 1);
this._state = "parse-snapshot-info";
}
case "parse-snapshot-info": {
var closingBracketIndex = WebInspector.findBalancedCurlyBrackets(this._json);
if (closingBracketIndex === -1)
return;
this._snapshot.snapshot = (JSON.parse(this._json.slice(0, closingBracketIndex)));
this._json = this._json.slice(closingBracketIndex);
this._state = "find-nodes";
}
case "find-nodes": {
var nodesToken = "\"nodes\"";
var nodesTokenIndex = this._json.indexOf(nodesToken);
if (nodesTokenIndex === -1)
return;
var bracketIndex = this._json.indexOf("[", nodesTokenIndex);
if (bracketIndex === -1)
return;
this._json = this._json.slice(bracketIndex + 1);
var node_fields_count = this._snapshot.snapshot.meta.node_fields.length;
var nodes_length = this._snapshot.snapshot.node_count * node_fields_count;
this._array = new Uint32Array(nodes_length);
this._arrayIndex = 0;
this._state = "parse-nodes";
}
case "parse-nodes": {
if (this._parseUintArray())
return;
this._snapshot.nodes = this._array;
this._state = "find-edges";
this._array = null;
}
case "find-edges": {
var edgesToken = "\"edges\"";
var edgesTokenIndex = this._json.indexOf(edgesToken);
if (edgesTokenIndex === -1)
return;
var bracketIndex = this._json.indexOf("[", edgesTokenIndex);
if (bracketIndex === -1)
return;
this._json = this._json.slice(bracketIndex + 1);
var edge_fields_count = this._snapshot.snapshot.meta.edge_fields.length;
var edges_length = this._snapshot.snapshot.edge_count * edge_fields_count;
this._array = new Uint32Array(edges_length);
this._arrayIndex = 0;
this._state = "parse-edges";
}
case "parse-edges": {
if (this._parseUintArray())
return;
this._snapshot.edges = this._array;
this._array = null;
this._state = "find-strings";
}
case "find-strings": {
var stringsToken = "\"strings\"";
var stringsTokenIndex = this._json.indexOf(stringsToken);
if (stringsTokenIndex === -1)
return;
var bracketIndex = this._json.indexOf("[", stringsTokenIndex);
if (bracketIndex === -1)
return;
this._json = this._json.slice(bracketIndex);
this._state = "accumulate-strings";
break;
}
case "accumulate-strings":
break;
}
}
};
;
WebInspector.HeapSnapshotWorkerWrapper = function()
{
}
WebInspector.HeapSnapshotWorkerWrapper.prototype = {
postMessage: function(message)
{
},
terminate: function()
{
},
__proto__: WebInspector.Object.prototype
}
WebInspector.HeapSnapshotRealWorker = function()
{
this._worker = new Worker("HeapSnapshotWorker.js");
this._worker.addEventListener("message", this._messageReceived.bind(this), false);
}
WebInspector.HeapSnapshotRealWorker.prototype = {
_messageReceived: function(event)
{
var message = event.data;
if ("callId" in message)
this.dispatchEventToListeners("message", message);
else {
if (message.object !== "console") {
console.log(WebInspector.UIString("Worker asks to call a method '%s' on an unsupported object '%s'.", message.method, message.object));
return;
}
if (message.method !== "log" && message.method !== "info" && message.method !== "error") {
console.log(WebInspector.UIString("Worker asks to call an unsupported method '%s' on the console object.", message.method));
return;
}
console[message.method].apply(window[message.object], message.arguments);
}
},
postMessage: function(message)
{
this._worker.postMessage(message);
},
terminate: function()
{
this._worker.terminate();
},
__proto__: WebInspector.HeapSnapshotWorkerWrapper.prototype
}
WebInspector.AsyncTaskQueue = function()
{
this._queue = [];
this._isTimerSheduled = false;
}
WebInspector.AsyncTaskQueue.prototype = {
addTask: function(task)
{
this._queue.push(task);
this._scheduleTimer();
},
_onTimeout: function()
{
this._isTimerSheduled = false;
var queue = this._queue;
this._queue = [];
for (var i = 0; i < queue.length; i++) {
try {
queue[i]();
} catch (e) {
console.error("Exception while running task: " + e.stack);
}
}
this._scheduleTimer();
},
_scheduleTimer: function()
{
if (this._queue.length && !this._isTimerSheduled) {
setTimeout(this._onTimeout.bind(this), 0);
this._isTimerSheduled = true;
}
}
}
WebInspector.HeapSnapshotFakeWorker = function()
{
this._dispatcher = new WebInspector.HeapSnapshotWorkerDispatcher(window, this._postMessageFromWorker.bind(this));
this._asyncTaskQueue = new WebInspector.AsyncTaskQueue();
}
WebInspector.HeapSnapshotFakeWorker.prototype = {
postMessage: function(message)
{
function dispatch()
{
if (this._dispatcher)
this._dispatcher.dispatchMessage({data: message});
}
this._asyncTaskQueue.addTask(dispatch.bind(this));
},
terminate: function()
{
this._dispatcher = null;
},
_postMessageFromWorker: function(message)
{
function send()
{
this.dispatchEventToListeners("message", message);
}
this._asyncTaskQueue.addTask(send.bind(this));
},
__proto__: WebInspector.HeapSnapshotWorkerWrapper.prototype
}
WebInspector.HeapSnapshotWorker = function()
{
this._nextObjectId = 1;
this._nextCallId = 1;
this._callbacks = [];
this._previousCallbacks = [];
this._worker = typeof InspectorTest === "undefined" ? new WebInspector.HeapSnapshotRealWorker() : new WebInspector.HeapSnapshotFakeWorker();
this._worker.addEventListener("message", this._messageReceived, this);
}
WebInspector.HeapSnapshotWorker.prototype = {
createLoader: function(snapshotConstructorName, proxyConstructor)
{
var objectId = this._nextObjectId++;
var proxy = new WebInspector.HeapSnapshotLoaderProxy(this, objectId, snapshotConstructorName, proxyConstructor);
this._postMessage({callId: this._nextCallId++, disposition: "create", objectId: objectId, methodName: "WebInspector.HeapSnapshotLoader"});
return proxy;
},
dispose: function()
{
this._worker.terminate();
if (this._interval)
clearInterval(this._interval);
},
disposeObject: function(objectId)
{
this._postMessage({callId: this._nextCallId++, disposition: "dispose", objectId: objectId});
},
callGetter: function(callback, objectId, getterName)
{
var callId = this._nextCallId++;
this._callbacks[callId] = callback;
this._postMessage({callId: callId, disposition: "getter", objectId: objectId, methodName: getterName});
},
callFactoryMethod: function(callback, objectId, methodName, proxyConstructor)
{
var callId = this._nextCallId++;
var methodArguments = Array.prototype.slice.call(arguments, 4);
var newObjectId = this._nextObjectId++;
if (callback) {
function wrapCallback(remoteResult)
{
callback(remoteResult ? new proxyConstructor(this, newObjectId) : null);
}
this._callbacks[callId] = wrapCallback.bind(this);
this._postMessage({callId: callId, disposition: "factory", objectId: objectId, methodName: methodName, methodArguments: methodArguments, newObjectId: newObjectId});
return null;
} else {
this._postMessage({callId: callId, disposition: "factory", objectId: objectId, methodName: methodName, methodArguments: methodArguments, newObjectId: newObjectId});
return new proxyConstructor(this, newObjectId);
}
},
callMethod: function(callback, objectId, methodName)
{
var callId = this._nextCallId++;
var methodArguments = Array.prototype.slice.call(arguments, 3);
if (callback)
this._callbacks[callId] = callback;
this._postMessage({callId: callId, disposition: "method", objectId: objectId, methodName: methodName, methodArguments: methodArguments});
},
startCheckingForLongRunningCalls: function()
{
if (this._interval)
return;
this._checkLongRunningCalls();
this._interval = setInterval(this._checkLongRunningCalls.bind(this), 300);
},
_checkLongRunningCalls: function()
{
for (var callId in this._previousCallbacks)
if (!(callId in this._callbacks))
delete this._previousCallbacks[callId];
var hasLongRunningCalls = false;
for (callId in this._previousCallbacks) {
hasLongRunningCalls = true;
break;
}
this.dispatchEventToListeners("wait", hasLongRunningCalls);
for (callId in this._callbacks)
this._previousCallbacks[callId] = true;
},
_findFunction: function(name)
{
var path = name.split(".");
var result = window;
for (var i = 0; i < path.length; ++i)
result = result[path[i]];
return result;
},
_messageReceived: function(event)
{
var data = event.data;
if (event.data.error) {
if (event.data.errorMethodName)
WebInspector.log(WebInspector.UIString("An error happened when a call for method '%s' was requested", event.data.errorMethodName));
WebInspector.log(event.data.errorCallStack);
delete this._callbacks[data.callId];
return;
}
if (!this._callbacks[data.callId])
return;
var callback = this._callbacks[data.callId];
delete this._callbacks[data.callId];
callback(data.result);
},
_postMessage: function(message)
{
this._worker.postMessage(message);
},
__proto__: WebInspector.Object.prototype
}
WebInspector.HeapSnapshotProxyObject = function(worker, objectId)
{
this._worker = worker;
this._objectId = objectId;
}
WebInspector.HeapSnapshotProxyObject.prototype = {
_callWorker: function(workerMethodName, args)
{
args.splice(1, 0, this._objectId);
return this._worker[workerMethodName].apply(this._worker, args);
},
dispose: function()
{
this._worker.disposeObject(this._objectId);
},
disposeWorker: function()
{
this._worker.dispose();
},
callFactoryMethod: function(callback, methodName, proxyConstructor, var_args)
{
return this._callWorker("callFactoryMethod", Array.prototype.slice.call(arguments, 0));
},
callGetter: function(callback, getterName)
{
return this._callWorker("callGetter", Array.prototype.slice.call(arguments, 0));
},
callMethod: function(callback, methodName, var_args)
{
return this._callWorker("callMethod", Array.prototype.slice.call(arguments, 0));
},
get worker() {
return this._worker;
}
};
WebInspector.HeapSnapshotLoaderProxy = function(worker, objectId, snapshotConstructorName, proxyConstructor)
{
WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId);
this._snapshotConstructorName = snapshotConstructorName;
this._proxyConstructor = proxyConstructor;
this._pendingSnapshotConsumers = [];
}
WebInspector.HeapSnapshotLoaderProxy.prototype = {
addConsumer: function(callback)
{
this._pendingSnapshotConsumers.push(callback);
},
write: function(chunk, callback)
{
this.callMethod(callback, "write", chunk);
},
close: function()
{
function buildSnapshot()
{
this.callFactoryMethod(updateStaticData.bind(this), "buildSnapshot", this._proxyConstructor, this._snapshotConstructorName);
}
function updateStaticData(snapshotProxy)
{
this.dispose();
snapshotProxy.updateStaticData(notifyPendingConsumers.bind(this));
}
function notifyPendingConsumers(snapshotProxy)
{
for (var i = 0; i < this._pendingSnapshotConsumers.length; ++i)
this._pendingSnapshotConsumers[i](snapshotProxy);
this._pendingSnapshotConsumers = [];
}
this.callMethod(buildSnapshot.bind(this), "close");
},
__proto__: WebInspector.HeapSnapshotProxyObject.prototype
}
WebInspector.HeapSnapshotProxy = function(worker, objectId)
{
WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId);
}
WebInspector.HeapSnapshotProxy.prototype = {
aggregates: function(sortedIndexes, key, filter, callback)
{
this.callMethod(callback, "aggregates", sortedIndexes, key, filter);
},
aggregatesForDiff: function(callback)
{
this.callMethod(callback, "aggregatesForDiff");
},
calculateSnapshotDiff: function(baseSnapshotId, baseSnapshotAggregates, callback)
{
this.callMethod(callback, "calculateSnapshotDiff", baseSnapshotId, baseSnapshotAggregates);
},
nodeClassName: function(snapshotObjectId, callback)
{
this.callMethod(callback, "nodeClassName", snapshotObjectId);
},
dominatorIdsForNode: function(nodeIndex, callback)
{
this.callMethod(callback, "dominatorIdsForNode", nodeIndex);
},
createEdgesProvider: function(nodeIndex, showHiddenData)
{
return this.callFactoryMethod(null, "createEdgesProvider", WebInspector.HeapSnapshotProviderProxy, nodeIndex, showHiddenData);
},
createRetainingEdgesProvider: function(nodeIndex, showHiddenData)
{
return this.callFactoryMethod(null, "createRetainingEdgesProvider", WebInspector.HeapSnapshotProviderProxy, nodeIndex, showHiddenData);
},
createAddedNodesProvider: function(baseSnapshotId, className)
{
return this.callFactoryMethod(null, "createAddedNodesProvider", WebInspector.HeapSnapshotProviderProxy, baseSnapshotId, className);
},
createDeletedNodesProvider: function(nodeIndexes)
{
return this.callFactoryMethod(null, "createDeletedNodesProvider", WebInspector.HeapSnapshotProviderProxy, nodeIndexes);
},
createNodesProvider: function(filter)
{
return this.callFactoryMethod(null, "createNodesProvider", WebInspector.HeapSnapshotProviderProxy, filter);
},
createNodesProviderForClass: function(className, aggregatesKey)
{
return this.callFactoryMethod(null, "createNodesProviderForClass", WebInspector.HeapSnapshotProviderProxy, className, aggregatesKey);
},
createNodesProviderForDominator: function(nodeIndex)
{
return this.callFactoryMethod(null, "createNodesProviderForDominator", WebInspector.HeapSnapshotProviderProxy, nodeIndex);
},
dispose: function()
{
this.disposeWorker();
},
get nodeCount()
{
return this._staticData.nodeCount;
},
get rootNodeIndex()
{
return this._staticData.rootNodeIndex;
},
updateStaticData: function(callback)
{
function dataReceived(staticData)
{
this._staticData = staticData;
callback(this);
}
this.callMethod(dataReceived.bind(this), "updateStaticData");
},
get totalSize()
{
return this._staticData.totalSize;
},
get uid()
{
return this._staticData.uid;
},
__proto__: WebInspector.HeapSnapshotProxyObject.prototype
}
WebInspector.NativeHeapSnapshotProxy = function(worker, objectId)
{
WebInspector.HeapSnapshotProxy.call(this, worker, objectId);
}
WebInspector.NativeHeapSnapshotProxy.prototype = {
images: function(callback)
{
this.callMethod(callback, "images");
},
__proto__: WebInspector.HeapSnapshotProxy.prototype
}
WebInspector.HeapSnapshotProviderProxy = function(worker, objectId)
{
WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId);
}
WebInspector.HeapSnapshotProviderProxy.prototype = {
nodePosition: function(snapshotObjectId, callback)
{
this.callMethod(callback, "nodePosition", snapshotObjectId);
},
isEmpty: function(callback)
{
this.callMethod(callback, "isEmpty");
},
serializeItemsRange: function(startPosition, endPosition, callback)
{
this.callMethod(callback, "serializeItemsRange", startPosition, endPosition);
},
sortAndRewind: function(comparator, callback)
{
this.callMethod(callback, "sortAndRewind", comparator);
},
__proto__: WebInspector.HeapSnapshotProxyObject.prototype
}
;
WebInspector.HeapSnapshotView = function(parent, profile)
{
WebInspector.View.call(this);
this.element.addStyleClass("heap-snapshot-view");
this.parent = parent;
this.parent.addEventListener("profile added", this._onProfileHeaderAdded, this);
this.viewsContainer = document.createElement("div");
this.viewsContainer.addStyleClass("views-container");
this.element.appendChild(this.viewsContainer);
this.containmentView = new WebInspector.View();
this.containmentView.element.addStyleClass("view");
this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid();
this.containmentDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
this.containmentDataGrid.show(this.containmentView.element);
this.containmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
this.constructorsView = new WebInspector.View();
this.constructorsView.element.addStyleClass("view");
this.constructorsView.element.appendChild(this._createToolbarWithClassNameFilter());
this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid();
this.constructorsDataGrid.element.addStyleClass("class-view-grid");
this.constructorsDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
this.constructorsDataGrid.show(this.constructorsView.element);
this.constructorsDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
this.diffView = new WebInspector.View();
this.diffView.element.addStyleClass("view");
this.diffView.element.appendChild(this._createToolbarWithClassNameFilter());
this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid();
this.diffDataGrid.element.addStyleClass("class-view-grid");
this.diffDataGrid.show(this.diffView.element);
this.diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
this.dominatorView = new WebInspector.View();
this.dominatorView.element.addStyleClass("view");
this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid();
this.dominatorDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
this.dominatorDataGrid.show(this.dominatorView.element);
this.dominatorDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
this.retainmentViewHeader = document.createElement("div");
this.retainmentViewHeader.addStyleClass("retainers-view-header");
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.addStyleClass("view");
this.retainmentView.element.addStyleClass("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.dataGrid = (this.constructorsDataGrid);
this.currentView = this.constructorsView;
this.viewSelectElement = document.createElement("select");
this.viewSelectElement.className = "status-bar-item";
this.viewSelectElement.addEventListener("change", this._onSelectedViewChanged.bind(this), false);
this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid},
{title: "Comparison", view: this.diffView, grid: this.diffDataGrid},
{title: "Containment", view: this.containmentView, grid: this.containmentDataGrid},
{title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid}];
this.views.current = 0;
for (var i = 0; i < this.views.length; ++i) {
var view = this.views[i];
var option = document.createElement("option");
option.label = WebInspector.UIString(view.title);
this.viewSelectElement.appendChild(option);
}
this._profileUid = profile.uid;
this._profileTypeId = profile.profileType().id;
this.baseSelectElement = document.createElement("select");
this.baseSelectElement.className = "status-bar-item";
this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
this._updateBaseOptions();
this.filterSelectElement = document.createElement("select");
this.filterSelectElement.className = "status-bar-item";
this.filterSelectElement.addEventListener("change", this._changeFilter.bind(this), false);
this._updateFilterOptions();
this.helpButton = new WebInspector.StatusBarButton("", "heap-snapshot-help-status-bar-item status-bar-item");
this.helpButton.addEventListener("click", this._helpClicked, this);
this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true);
this.profile.load(profileCallback.bind(this));
function profileCallback(heapSnapshotProxy)
{
var list = this._profiles();
var profileIndex;
for (var i = 0; i < list.length; ++i) {
if (list[i].uid === this._profileUid) {
profileIndex = i;
break;
}
}
if (profileIndex > 0)
this.baseSelectElement.selectedIndex = profileIndex - 1;
else
this.baseSelectElement.selectedIndex = profileIndex;
this.dataGrid.setDataSource(heapSnapshotProxy);
}
}
WebInspector.HeapSnapshotView.prototype = {
dispose: function()
{
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()
{
function appendArrowImage(element, hidden)
{
var span = document.createElement("span");
span.className = "status-bar-select-container" + (hidden ? " hidden" : "");
span.appendChild(element);
return span;
}
return [appendArrowImage(this.viewSelectElement), appendArrowImage(this.baseSelectElement, true), appendArrowImage(this.filterSelectElement), this.helpButton.element];
},
get profile()
{
return this.parent.getProfile(this._profileTypeId, this._profileUid);
},
get baseProfile()
{
return this.parent.getProfile(this._profileTypeId, this._baseProfileUid);
},
wasShown: function()
{
this.profile.load(profileCallback1.bind(this));
function profileCallback1() {
if (this.baseProfile)
this.baseProfile.load(profileCallback2.bind(this));
else
profileCallback2.call(this);
}
function profileCallback2() {
this.currentView.show(this.viewsContainer);
}
},
willHide: function()
{
this._currentSearchResultIndex = -1;
this._popoverHelper.hidePopover();
if (this.helpPopover && this.helpPopover.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 = [];
},
performSearch: function(query, finishedCallback)
{
this.searchCanceled();
query = query.trim();
if (!query.length)
return;
if (this.currentView !== this.constructorsView && this.currentView !== this.diffView)
return;
this._searchFinishedCallback = finishedCallback;
function matchesByName(gridNode) {
return ("_name" in gridNode) && gridNode._name.hasSubstring(query, true);
}
function matchesById(gridNode) {
return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === query;
}
var matchPredicate;
if (query.charAt(0) !== "@")
matchPredicate = matchesByName;
else {
query = parseInt(query.substring(1), 10);
matchPredicate = matchesById;
}
function matchesQuery(gridNode)
{
delete gridNode._searchMatched;
if (matchPredicate(gridNode)) {
gridNode._searchMatched = true;
gridNode.refresh();
return true;
}
return false;
}
var current = this.dataGrid.rootNode().children[0];
var depth = 0;
var info = {};
const maxDepth = 1;
while (current) {
if (matchesQuery(current))
this._searchResults.push({ node: current });
current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
depth += info.depthChange;
}
finishedCallback(this, this._searchResults.length);
},
jumpToFirstSearchResult: function()
{
if (!this._searchResults || !this._searchResults.length)
return;
this._currentSearchResultIndex = 0;
this._jumpToSearchResult(this._currentSearchResultIndex);
},
jumpToLastSearchResult: function()
{
if (!this._searchResults || !this._searchResults.length)
return;
this._currentSearchResultIndex = (this._searchResults.length - 1);
this._jumpToSearchResult(this._currentSearchResultIndex);
},
jumpToNextSearchResult: function()
{
if (!this._searchResults || !this._searchResults.length)
return;
if (++this._currentSearchResultIndex >= this._searchResults.length)
this._currentSearchResultIndex = 0;
this._jumpToSearchResult(this._currentSearchResultIndex);
},
jumpToPreviousSearchResult: function()
{
if (!this._searchResults || !this._searchResults.length)
return;
if (--this._currentSearchResultIndex < 0)
this._currentSearchResultIndex = (this._searchResults.length - 1);
this._jumpToSearchResult(this._currentSearchResultIndex);
},
showingFirstSearchResult: function()
{
return (this._currentSearchResultIndex === 0);
},
showingLastSearchResult: function()
{
return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
},
_jumpToSearchResult: function(index)
{
var searchResult = this._searchResults[index];
if (!searchResult)
return;
var node = searchResult.node;
node.revealAndSelect();
},
refreshVisibleData: function()
{
var child = this.dataGrid.rootNode().children[0];
while (child) {
child.refresh();
child = child.traverseNextNode(false, null, true);
}
},
_changeBase: function()
{
if (this._baseProfileUid === this._profiles()[this.baseSelectElement.selectedIndex].uid)
return;
this._baseProfileUid = this._profiles()[this.baseSelectElement.selectedIndex].uid;
var dataGrid = (this.dataGrid);
if (dataGrid.snapshot)
this.baseProfile.load(dataGrid.setBaseDataSource.bind(dataGrid));
if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
return;
this._searchFinishedCallback(this, -this._searchResults.length);
this.performSearch(this.currentQuery, this._searchFinishedCallback);
},
_changeFilter: function()
{
var profileIndex = this.filterSelectElement.selectedIndex - 1;
this.dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex);
WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
action: WebInspector.UserMetrics.UserActionNames.HeapSnapshotFilterChanged,
label: this.filterSelectElement[this.filterSelectElement.selectedIndex].label
});
if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
return;
this._searchFinishedCallback(this, -this._searchResults.length);
this.performSearch(this.currentQuery, this._searchFinishedCallback);
},
_createToolbarWithClassNameFilter: function()
{
var toolbar = document.createElement("div");
toolbar.addStyleClass("class-view-toolbar");
var classNameFilter = document.createElement("input");
classNameFilter.addStyleClass("class-name-filter");
classNameFilter.setAttribute("placeholder", WebInspector.UIString("Class filter"));
classNameFilter.addEventListener("keyup", this._changeNameFilter.bind(this, classNameFilter), false);
toolbar.appendChild(classNameFilter);
return toolbar;
},
_changeNameFilter: function(classNameInputElement)
{
var filter = classNameInputElement.value;
this.dataGrid.changeNameFilter(filter);
},
_profiles: function()
{
return this.parent.getProfileType(this._profileTypeId).getProfiles();
},
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.hasStyleClass("count-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("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) {
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.viewSelectElement.selectedIndex = 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.baseSelectElement.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.baseSelectElement.parentElement.removeStyleClass("hidden");
else
this.baseSelectElement.parentElement.addStyleClass("hidden");
if (this.currentView === this.constructorsView)
this.filterSelectElement.parentElement.removeStyleClass("hidden");
else
this.filterSelectElement.parentElement.addStyleClass("hidden");
},
_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;
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);
},
_helpClicked: function(event)
{
if (!this._helpPopoverContentElement) {
var refTypes = ["a:", "console-formatted-name", WebInspector.UIString("property"),
"0:", "console-formatted-name", WebInspector.UIString("element"),
"a:", "console-formatted-number", WebInspector.UIString("context var"),
"a:", "console-formatted-null", WebInspector.UIString("system prop")];
var objTypes = [" a ", "console-formatted-object", "Object",
"\"a\"", "console-formatted-string", "String",
"/a/", "console-formatted-string", "RegExp",
"a()", "console-formatted-function", "Function",
"a[]", "console-formatted-object", "Array",
"num", "console-formatted-number", "Number",
" a ", "console-formatted-null", "System"];
var contentElement = document.createElement("table");
contentElement.className = "heap-snapshot-help";
var headerRow = document.createElement("tr");
var propsHeader = document.createElement("th");
propsHeader.textContent = WebInspector.UIString("Property types:");
headerRow.appendChild(propsHeader);
var objsHeader = document.createElement("th");
objsHeader.textContent = WebInspector.UIString("Object types:");
headerRow.appendChild(objsHeader);
contentElement.appendChild(headerRow);
function appendHelp(help, index, cell)
{
var div = document.createElement("div");
div.className = "source-code event-properties";
var name = document.createElement("span");
name.textContent = help[index];
name.className = help[index + 1];
div.appendChild(name);
var desc = document.createElement("span");
desc.textContent = " " + help[index + 2];
div.appendChild(desc);
cell.appendChild(div);
}
var len = Math.max(refTypes.length, objTypes.length);
for (var i = 0; i < len; i += 3) {
var row = document.createElement("tr");
var refCell = document.createElement("td");
if (refTypes[i])
appendHelp(refTypes, i, refCell);
row.appendChild(refCell);
var objCell = document.createElement("td");
if (objTypes[i])
appendHelp(objTypes, i, objCell);
row.appendChild(objCell);
contentElement.appendChild(row);
}
this._helpPopoverContentElement = contentElement;
this.helpPopover = new WebInspector.Popover();
}
if (this.helpPopover.isShowing())
this.helpPopover.hide();
else
this.helpPopover.show(this._helpPopoverContentElement, this.helpButton.element);
},
_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";
this.retainmentView.element.style.height = height + "px";
this.retainmentViewHeader.style.bottom = height + "px";
this.currentView.doResize();
},
_updateBaseOptions: function()
{
var list = this._profiles();
if (this.baseSelectElement.length === list.length)
return;
for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
var baseOption = document.createElement("option");
var title = list[i].title;
if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(title))
title = WebInspector.UIString("Snapshot %d", WebInspector.ProfilesPanelDescriptor.userInitiatedProfileIndex(title));
baseOption.label = title;
this.baseSelectElement.appendChild(baseOption);
}
},
_updateFilterOptions: function()
{
var list = this._profiles();
if (this.filterSelectElement.length - 1 === list.length)
return;
if (!this.filterSelectElement.length) {
var filterOption = document.createElement("option");
filterOption.label = WebInspector.UIString("All objects");
this.filterSelectElement.appendChild(filterOption);
}
if (this.profile.fromFile())
return;
for (var i = this.filterSelectElement.length - 1, n = list.length; i < n; ++i) {
var profile = list[i];
var filterOption = document.createElement("option");
var title = list[i].title;
if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(title)) {
var profileIndex = WebInspector.ProfilesPanelDescriptor.userInitiatedProfileIndex(title);
if (!i)
title = WebInspector.UIString("Objects allocated before Snapshot %d", profileIndex);
else
title = WebInspector.UIString("Objects allocated between Snapshots %d and %d", profileIndex - 1, profileIndex);
}
filterOption.label = title;
this.filterSelectElement.appendChild(filterOption);
}
},
_onProfileHeaderAdded: function(event)
{
if (!event.data || event.data.type !== this._profileTypeId)
return;
this._updateBaseOptions();
this._updateFilterOptions();
},
__proto__: WebInspector.View.prototype
}
WebInspector.HeapSnapshotProfileType = function()
{
WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("Take Heap Snapshot"));
InspectorBackend.registerHeapProfilerDispatcher(this);
}
WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
WebInspector.HeapSnapshotProfileType.prototype = {
fileExtension: function()
{
return ".heapsnapshot";
},
get buttonTooltip()
{
return WebInspector.UIString("Take heap snapshot.");
},
isInstantProfile: function()
{
return true;
},
buttonClicked: function()
{
this._takeHeapSnapshot();
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.");
},
createTemporaryProfile: function(title)
{
title = title || WebInspector.UIString("Snapshotting\u2026");
return new WebInspector.HeapProfileHeader(this, title);
},
createProfile: function(profile)
{
return new WebInspector.HeapProfileHeader(this, profile.title, profile.uid, profile.maxJSObjectId || 0);
},
_takeHeapSnapshot: function()
{
var temporaryProfile = this.findTemporaryProfile();
if (!temporaryProfile)
this.addProfile(this.createTemporaryProfile());
HeapProfilerAgent.takeHeapSnapshot(true, function() {});
WebInspector.userMetrics.ProfilesHeapProfileTaken.record();
},
addProfileHeader: function(profileHeader)
{
this.addProfile(this.createProfile(profileHeader));
},
addHeapSnapshotChunk: function(uid, chunk)
{
var profile = this._profilesIdMap[this._makeKey(uid)];
if (profile)
profile.transferChunk(chunk);
},
finishHeapSnapshot: function(uid)
{
var profile = this._profilesIdMap[this._makeKey(uid)];
if (profile)
profile.finishHeapSnapshot();
},
reportHeapSnapshotProgress: function(done, total)
{
var profile = this.findTemporaryProfile();
if (profile)
this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProgressUpdated, {"profile": profile, "done": done, "total": total});
},
resetProfiles: function()
{
this._reset();
},
removeProfile: function(profile)
{
WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
if (!profile.isTemporary)
HeapProfilerAgent.removeProfile(profile.uid);
},
_requestProfilesFromBackend: function(populateCallback)
{
HeapProfilerAgent.getProfileHeaders(populateCallback);
},
__proto__: WebInspector.ProfileType.prototype
}
WebInspector.HeapProfileHeader = function(type, title, uid, maxJSObjectId)
{
WebInspector.ProfileHeader.call(this, type, title, uid);
this.maxJSObjectId = maxJSObjectId;
this._receiver = null;
this._snapshotProxy = null;
this._totalNumberOfChunks = 0;
}
WebInspector.HeapProfileHeader.prototype = {
createSidebarTreeElement: function()
{
return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
},
createView: function(profilesPanel)
{
return new WebInspector.HeapSnapshotView(profilesPanel, this);
},
load: function(callback)
{
if (this._snapshotProxy) {
callback(this._snapshotProxy);
return;
}
this._numberOfChunks = 0;
this._savedChunks = 0;
this._savingToFile = false;
if (!this._receiver) {
this._setupWorker();
this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
this.sidebarElement.wait = true;
this.startSnapshotTransfer();
}
var loaderProxy = (this._receiver);
loaderProxy.addConsumer(callback);
},
startSnapshotTransfer: function()
{
HeapProfilerAgent.getHeapSnapshot(this.uid);
},
snapshotConstructorName: function()
{
return "JSHeapSnapshot";
},
snapshotProxyConstructor: function()
{
return WebInspector.HeapSnapshotProxy;
},
_setupWorker: function()
{
function setProfileWait(event)
{
this.sidebarElement.wait = event.data;
}
var worker = new WebInspector.HeapSnapshotWorker();
worker.addEventListener("wait", setProfileWait, this);
var loaderProxy = worker.createLoader(this.snapshotConstructorName(), this.snapshotProxyConstructor());
loaderProxy.addConsumer(this._snapshotReceived.bind(this));
this._receiver = loaderProxy;
},
dispose: function()
{
if (this._receiver)
this._receiver.close();
else if (this._snapshotProxy)
this._snapshotProxy.dispose();
},
_updateTransferProgress: function(value, maxValue)
{
var percentValue = ((maxValue ? (value / maxValue) : 0) * 100).toFixed(0);
if (this._savingToFile)
this.sidebarElement.subtitle = WebInspector.UIString("Saving\u2026 %d\%", percentValue);
else
this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026 %d\%", percentValue);
},
_updateSnapshotStatus: function()
{
this.sidebarElement.subtitle = Number.bytesToString(this._snapshotProxy.totalSize);
this.sidebarElement.wait = false;
},
transferChunk: function(chunk)
{
++this._numberOfChunks;
this._receiver.write(chunk, callback.bind(this));
function callback()
{
this._updateTransferProgress(++this._savedChunks, this._totalNumberOfChunks);
if (this._totalNumberOfChunks === this._savedChunks) {
if (this._savingToFile)
this._updateSnapshotStatus();
else
this.sidebarElement.subtitle = WebInspector.UIString("Parsing\u2026");
this._receiver.close();
}
}
},
_snapshotReceived: function(snapshotProxy)
{
this._receiver = null;
if (snapshotProxy)
this._snapshotProxy = snapshotProxy;
this._updateSnapshotStatus();
var worker = (this._snapshotProxy.worker);
this.isTemporary = false;
worker.startCheckingForLongRunningCalls();
},
finishHeapSnapshot: function()
{
this._totalNumberOfChunks = this._numberOfChunks;
},
canSaveToFile: function()
{
return !this.fromFile() && !!this._snapshotProxy && !this._receiver;
},
saveToFile: function()
{
this._numberOfChunks = 0;
var fileOutputStream = new WebInspector.FileOutputStream();
function onOpen()
{
this._receiver = fileOutputStream;
this._savedChunks = 0;
this._updateTransferProgress(0, this._totalNumberOfChunks);
HeapProfilerAgent.getHeapSnapshot(this.uid);
}
this._savingToFile = true;
this._fileName = this._fileName || "Heap-" + new Date().toISO8601Compact() + this._profileType.fileExtension();
fileOutputStream.open(this._fileName, onOpen.bind(this));
},
loadFromFile: function(file)
{
this.title = file.name;
this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
this.sidebarElement.wait = true;
this._setupWorker();
this._numberOfChunks = 0;
this._savingToFile = false;
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
}
WebInspector.HeapSnapshotLoadFromFileDelegate = function(snapshotHeader)
{
this._snapshotHeader = snapshotHeader;
}
WebInspector.HeapSnapshotLoadFromFileDelegate.prototype = {
onTransferStarted: function()
{
},
onChunkTransferred: function(reader)
{
this._snapshotHeader._updateTransferProgress(reader.loadedSize(), reader.fileSize());
},
onTransferFinished: function()
{
this._snapshotHeader.finishHeapSnapshot();
},
onError: function (reader, e)
{
switch(e.target.error.code) {
case e.target.error.NOT_FOUND_ERR:
this._snapshotHeader.sidebarElement.subtitle = WebInspector.UIString("'%s' not found.", reader.fileName());
break;
case e.target.error.NOT_READABLE_ERR:
this._snapshotHeader.sidebarElement.subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName());
break;
case e.target.error.ABORT_ERR:
break;
default:
this._snapshotHeader.sidebarElement.subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code);
}
}
}
;
WebInspector.HeapSnapshotWorkerDispatcher = function(globalObject, postMessage)
{
this._objects = [];
this._global = globalObject;
this._postMessage = postMessage;
}
WebInspector.HeapSnapshotWorkerDispatcher.prototype = {
_findFunction: function(name)
{
var path = name.split(".");
var result = this._global;
for (var i = 0; i < path.length; ++i)
result = result[path[i]];
return result;
},
dispatchMessage: function(event)
{
var data = event.data;
var response = {callId: data.callId};
try {
switch (data.disposition) {
case "create": {
var constructorFunction = this._findFunction(data.methodName);
this._objects[data.objectId] = new constructorFunction();
break;
}
case "dispose": {
delete this._objects[data.objectId];
break;
}
case "getter": {
var object = this._objects[data.objectId];
var result = object[data.methodName];
response.result = result;
break;
}
case "factory": {
var object = this._objects[data.objectId];
var result = object[data.methodName].apply(object, data.methodArguments);
if (result)
this._objects[data.newObjectId] = result;
response.result = !!result;
break;
}
case "method": {
var object = this._objects[data.objectId];
response.result = object[data.methodName].apply(object, data.methodArguments);
break;
}
}
} catch (e) {
response.error = e.toString();
response.errorCallStack = e.stack;
if (data.methodName)
response.errorMethodName = data.methodName;
}
this._postMessage(response);
}
};
;
WebInspector.JSHeapSnapshot = function(profile)
{
this._nodeFlags = {
canBeQueried: 1,
detachedDOMTreeNode: 2,
pageObject: 4,
visitedMarkerMask: 0x0ffff,
visitedMarker: 0x10000
};
WebInspector.HeapSnapshot.call(this, profile);
}
WebInspector.JSHeapSnapshot.prototype = {
createNode: function(nodeIndex)
{
return new WebInspector.JSHeapSnapshotNode(this, nodeIndex);
},
createEdge: function(edges, edgeIndex)
{
return new WebInspector.JSHeapSnapshotEdge(this, edges, edgeIndex);
},
createRetainingEdge: function(retainedNodeIndex, retainerIndex)
{
return new WebInspector.JSHeapSnapshotRetainerEdge(this, retainedNodeIndex, retainerIndex);
},
classNodesFilter: function()
{
function filter(node)
{
return node.isUserObject();
}
return filter;
},
containmentEdgesFilter: function(showHiddenData)
{
function filter(edge) {
if (edge.isInvisible())
return false;
if (showHiddenData)
return true;
return !edge.isHidden() && !edge.node().isHidden();
}
return filter;
},
retainingEdgesFilter: function(showHiddenData)
{
var containmentEdgesFilter = this.containmentEdgesFilter(showHiddenData);
function filter(edge) {
if (!containmentEdgesFilter(edge))
return false;
return edge.node().id() !== 1 && !edge.node().isSynthetic() && !edge.isWeak();
}
return filter;
},
dispose: function()
{
WebInspector.HeapSnapshot.prototype.dispose.call(this);
delete this._flags;
},
_markInvisibleEdges: function()
{
for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
var edge = iter.edge;
if (!edge.isShortcut())
continue;
var node = edge.node();
var propNames = {};
for (var innerIter = node.edges(); innerIter.hasNext(); innerIter.next()) {
var globalObjEdge = innerIter.edge;
if (globalObjEdge.isShortcut())
propNames[globalObjEdge._nameOrIndex()] = true;
}
for (innerIter.rewind(); innerIter.hasNext(); innerIter.next()) {
var globalObjEdge = innerIter.edge;
if (!globalObjEdge.isShortcut()
&& globalObjEdge.node().isHidden()
&& globalObjEdge._hasStringName()
&& (globalObjEdge._nameOrIndex() in propNames))
this._containmentEdges[globalObjEdge._edges._start + globalObjEdge.edgeIndex + this._edgeTypeOffset] = this._edgeInvisibleType;
}
}
},
_calculateFlags: function()
{
this._flags = new Uint32Array(this.nodeCount);
this._markDetachedDOMTreeNodes();
this._markQueriableHeapObjects();
this._markPageOwnedNodes();
},
distanceForUserRoot: function(node)
{
if (node.isWindow())
return 1;
if (node.isDocumentDOMTreesRoot())
return 0;
return -1;
},
userObjectsMapAndFlag: function()
{
return {
map: this._flags,
flag: this._nodeFlags.pageObject
};
},
_flagsOfNode: function(node)
{
return this._flags[node.nodeIndex / this._nodeFieldCount];
},
_markDetachedDOMTreeNodes: function()
{
var flag = this._nodeFlags.detachedDOMTreeNode;
var detachedDOMTreesRoot;
for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
var node = iter.edge.node();
if (node.name() === "(Detached DOM trees)") {
detachedDOMTreesRoot = node;
break;
}
}
if (!detachedDOMTreesRoot)
return;
var detachedDOMTreeRE = /^Detached DOM tree/;
for (var iter = detachedDOMTreesRoot.edges(); iter.hasNext(); iter.next()) {
var node = iter.edge.node();
if (detachedDOMTreeRE.test(node.className())) {
for (var edgesIter = node.edges(); edgesIter.hasNext(); edgesIter.next())
this._flags[edgesIter.edge.node().nodeIndex / this._nodeFieldCount] |= flag;
}
}
},
_markQueriableHeapObjects: function()
{
var flag = this._nodeFlags.canBeQueried;
var hiddenEdgeType = this._edgeHiddenType;
var internalEdgeType = this._edgeInternalType;
var invisibleEdgeType = this._edgeInvisibleType;
var weakEdgeType = this._edgeWeakType;
var edgeToNodeOffset = this._edgeToNodeOffset;
var edgeTypeOffset = this._edgeTypeOffset;
var edgeFieldsCount = this._edgeFieldsCount;
var containmentEdges = this._containmentEdges;
var nodes = this._nodes;
var nodeCount = this.nodeCount;
var nodeFieldCount = this._nodeFieldCount;
var firstEdgeIndexes = this._firstEdgeIndexes;
var flags = this._flags;
var list = [];
for (var iter = this.rootNode().edges(); iter.hasNext(); iter.next()) {
if (iter.edge.node().isWindow())
list.push(iter.edge.node().nodeIndex / nodeFieldCount);
}
while (list.length) {
var nodeOrdinal = list.pop();
if (flags[nodeOrdinal] & flag)
continue;
flags[nodeOrdinal] |= flag;
var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
var childNodeOrdinal = childNodeIndex / nodeFieldCount;
if (flags[childNodeOrdinal] & flag)
continue;
var type = containmentEdges[edgeIndex + edgeTypeOffset];
if (type === hiddenEdgeType || type === invisibleEdgeType || type === internalEdgeType || type === weakEdgeType)
continue;
list.push(childNodeOrdinal);
}
}
},
_markPageOwnedNodes: function()
{
var edgeShortcutType = this._edgeShortcutType;
var edgeElementType = this._edgeElementType;
var edgeToNodeOffset = this._edgeToNodeOffset;
var edgeTypeOffset = this._edgeTypeOffset;
var edgeFieldsCount = this._edgeFieldsCount;
var edgeWeakType = this._edgeWeakType;
var firstEdgeIndexes = this._firstEdgeIndexes;
var containmentEdges = this._containmentEdges;
var containmentEdgesLength = containmentEdges.length;
var nodes = this._nodes;
var nodeFieldCount = this._nodeFieldCount;
var nodesCount = this.nodeCount;
var flags = this._flags;
var flag = this._nodeFlags.pageObject;
var visitedMarker = this._nodeFlags.visitedMarker;
var visitedMarkerMask = this._nodeFlags.visitedMarkerMask;
var markerAndFlag = visitedMarker | flag;
var nodesToVisit = new Uint32Array(nodesCount);
var nodesToVisitLength = 0;
var rootNodeOrdinal = this._rootNodeIndex / nodeFieldCount;
var node = this.rootNode();
for (var edgeIndex = firstEdgeIndexes[rootNodeOrdinal], endEdgeIndex = firstEdgeIndexes[rootNodeOrdinal + 1];
edgeIndex < endEdgeIndex;
edgeIndex += edgeFieldsCount) {
var edgeType = containmentEdges[edgeIndex + edgeTypeOffset];
var nodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
if (edgeType === edgeElementType) {
node.nodeIndex = nodeIndex;
if (!node.isDocumentDOMTreesRoot())
continue;
} else if (edgeType !== edgeShortcutType)
continue;
var nodeOrdinal = nodeIndex / nodeFieldCount;
nodesToVisit[nodesToVisitLength++] = nodeOrdinal;
flags[nodeOrdinal] |= visitedMarker;
}
while (nodesToVisitLength) {
var nodeOrdinal = nodesToVisit[--nodesToVisitLength];
flags[nodeOrdinal] |= flag;
flags[nodeOrdinal] &= visitedMarkerMask;
var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
var childNodeOrdinal = childNodeIndex / nodeFieldCount;
if (flags[childNodeOrdinal] & markerAndFlag)
continue;
var type = containmentEdges[edgeIndex + edgeTypeOffset];
if (type === edgeWeakType)
continue;
nodesToVisit[nodesToVisitLength++] = childNodeOrdinal;
flags[childNodeOrdinal] |= visitedMarker;
}
}
},
__proto__: WebInspector.HeapSnapshot.prototype
};
WebInspector.JSHeapSnapshotNode = function(snapshot, nodeIndex)
{
WebInspector.HeapSnapshotNode.call(this, snapshot, nodeIndex)
}
WebInspector.JSHeapSnapshotNode.prototype = {
canBeQueried: function()
{
var flags = this._snapshot._flagsOfNode(this);
return !!(flags & this._snapshot._nodeFlags.canBeQueried);
},
isUserObject: function()
{
var flags = this._snapshot._flagsOfNode(this);
return !!(flags & this._snapshot._nodeFlags.pageObject);
},
className: function()
{
var type = this.type();
switch (type) {
case "hidden":
return "(system)";
case "object":
case "native":
return this.name();
case "code":
return "(compiled code)";
default:
return "(" + type + ")";
}
},
classIndex: function()
{
var snapshot = this._snapshot;
var nodes = snapshot._nodes;
var type = nodes[this.nodeIndex + snapshot._nodeTypeOffset];;
if (type === snapshot._nodeObjectType || type === snapshot._nodeNativeType)
return nodes[this.nodeIndex + snapshot._nodeNameOffset];
return -1 - type;
},
id: function()
{
var snapshot = this._snapshot;
return snapshot._nodes[this.nodeIndex + snapshot._nodeIdOffset];
},
isHidden: function()
{
return this._type() === this._snapshot._nodeHiddenType;
},
isSynthetic: function()
{
return this._type() === this._snapshot._nodeSyntheticType;
},
isWindow: function()
{
const windowRE = /^Window/;
return windowRE.test(this.name());
},
isDocumentDOMTreesRoot: function()
{
return this.isSynthetic() && this.name() === "(Document DOM trees)";
},
serialize: function()
{
var result = WebInspector.HeapSnapshotNode.prototype.serialize.call(this);
var flags = this._snapshot._flagsOfNode(this);
if (flags & this._snapshot._nodeFlags.canBeQueried)
result.canBeQueried = true;
if (flags & this._snapshot._nodeFlags.detachedDOMTreeNode)
result.detachedDOMTreeNode = true;
return result;
},
__proto__: WebInspector.HeapSnapshotNode.prototype
};
WebInspector.JSHeapSnapshotEdge = function(snapshot, edges, edgeIndex)
{
WebInspector.HeapSnapshotEdge.call(this, snapshot, edges, edgeIndex);
}
WebInspector.JSHeapSnapshotEdge.prototype = {
clone: function()
{
return new WebInspector.JSHeapSnapshotEdge(this._snapshot, this._edges, this.edgeIndex);
},
hasStringName: function()
{
if (!this.isShortcut())
return this._hasStringName();
return isNaN(parseInt(this._name(), 10));
},
isElement: function()
{
return this._type() === this._snapshot._edgeElementType;
},
isHidden: function()
{
return this._type() === this._snapshot._edgeHiddenType;
},
isWeak: function()
{
return this._type() === this._snapshot._edgeWeakType;
},
isInternal: function()
{
return this._type() === this._snapshot._edgeInternalType;
},
isInvisible: function()
{
return this._type() === this._snapshot._edgeInvisibleType;
},
isShortcut: function()
{
return this._type() === this._snapshot._edgeShortcutType;
},
name: function()
{
if (!this.isShortcut())
return this._name();
var numName = parseInt(this._name(), 10);
return isNaN(numName) ? this._name() : numName;
},
toString: function()
{
var name = this.name();
switch (this.type()) {
case "context": return "->" + name;
case "element": return "[" + name + "]";
case "weak": return "[[" + name + "]]";
case "property":
return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]";
case "shortcut":
if (typeof name === "string")
return name.indexOf(" ") === -1 ? "." + name : "[\"" + name + "\"]";
else
return "[" + name + "]";
case "internal":
case "hidden":
case "invisible":
return "{" + name + "}";
};
return "?" + name + "?";
},
_hasStringName: function()
{
return !this.isElement() && !this.isHidden() && !this.isWeak();
},
_name: function()
{
return this._hasStringName() ? this._snapshot._strings[this._nameOrIndex()] : this._nameOrIndex();
},
_nameOrIndex: function()
{
return this._edges.item(this.edgeIndex + this._snapshot._edgeNameOffset);
},
_type: function()
{
return this._edges.item(this.edgeIndex + this._snapshot._edgeTypeOffset);
},
__proto__: WebInspector.HeapSnapshotEdge.prototype
};
WebInspector.JSHeapSnapshotRetainerEdge = function(snapshot, retainedNodeIndex, retainerIndex)
{
WebInspector.HeapSnapshotRetainerEdge.call(this, snapshot, retainedNodeIndex, retainerIndex);
}
WebInspector.JSHeapSnapshotRetainerEdge.prototype = {
clone: function()
{
return new WebInspector.JSHeapSnapshotRetainerEdge(this._snapshot, this._retainedNodeIndex, this.retainerIndex());
},
isHidden: function()
{
return this._edge().isHidden();
},
isInternal: function()
{
return this._edge().isInternal();
},
isInvisible: function()
{
return this._edge().isInvisible();
},
isShortcut: function()
{
return this._edge().isShortcut();
},
isWeak: function()
{
return this._edge().isWeak();
},
__proto__: WebInspector.HeapSnapshotRetainerEdge.prototype
}
;
WebInspector.NativeHeapSnapshot = function(profile)
{
WebInspector.HeapSnapshot.call(this, profile);
this._nodeObjectType = this._metaNode.type_strings["object"];
this._edgeWeakType = this._metaNode.type_strings["weak"];
this._edgeElementType = this._metaNode.type_strings["property"];
}
WebInspector.NativeHeapSnapshot.prototype = {
createNode: function(nodeIndex)
{
return new WebInspector.NativeHeapSnapshotNode(this, nodeIndex);
},
createEdge: function(edges, edgeIndex)
{
return new WebInspector.NativeHeapSnapshotEdge(this, edges, edgeIndex);
},
createRetainingEdge: function(retainedNodeIndex, retainerIndex)
{
return new WebInspector.NativeHeapSnapshotRetainerEdge(this, retainedNodeIndex, retainerIndex);
},
_markInvisibleEdges: function()
{
},
_calculateFlags: function()
{
},
userObjectsMapAndFlag: function()
{
return null;
},
images: function()
{
var aggregatesByClassName = this.aggregates(false, "allObjects");
var result = [];
var cachedImages = aggregatesByClassName["WebCore::CachedImage"];
function getImageName(node)
{
return node.name();
}
this._addNodes(cachedImages, getImageName, result);
var canvases = aggregatesByClassName["WebCore::HTMLCanvasElement"];
function getCanvasName(node)
{
return "HTMLCanvasElement";
}
this._addNodes(canvases, getCanvasName, result);
return result;
},
_addNodes: function(classData, nameResolver, result)
{
if (!classData)
return;
var node = this.rootNode();
for (var i = 0; i < classData.idxs.length; i++) {
node.nodeIndex = classData.idxs[i];
result.push({
name: nameResolver(node),
size: node.retainedSize(),
});
}
},
__proto__: WebInspector.HeapSnapshot.prototype
};
WebInspector.NativeHeapSnapshotNode = function(snapshot, nodeIndex)
{
WebInspector.HeapSnapshotNode.call(this, snapshot, nodeIndex)
}
WebInspector.NativeHeapSnapshotNode.prototype = {
className: function()
{
return this._snapshot._strings[this.classIndex()];
},
classIndex: function()
{
return this._snapshot._nodes[this.nodeIndex + this._snapshot._nodeTypeOffset];
},
id: function()
{
return this._snapshot._nodes[this.nodeIndex + this._snapshot._nodeIdOffset];
},
name: function()
{
return this._snapshot._strings[this._snapshot._nodes[this.nodeIndex + this._snapshot._nodeNameOffset]];;
},
serialize: function()
{
return {
id: this.id(),
name: this.className(),
displayName: this.name(),
distance: this.distance(),
nodeIndex: this.nodeIndex,
retainedSize: this.retainedSize(),
selfSize: this.selfSize(),
type: this._snapshot._nodeObjectType
};
},
isHidden: function()
{
return false;
},
isSynthetic: function()
{
return false;
},
__proto__: WebInspector.HeapSnapshotNode.prototype
};
WebInspector.NativeHeapSnapshotEdge = function(snapshot, edges, edgeIndex)
{
WebInspector.HeapSnapshotEdge.call(this, snapshot, edges, edgeIndex);
}
WebInspector.NativeHeapSnapshotEdge.prototype = {
clone: function()
{
return new WebInspector.NativeHeapSnapshotEdge(this._snapshot, this._edges, this.edgeIndex);
},
hasStringName: function()
{
return true;
},
isHidden: function()
{
return false;
},
isWeak: function()
{
return false;
},
isInternal: function()
{
return false;
},
isInvisible: function()
{
return false;
},
isShortcut: function()
{
return false;
},
name: function()
{
return this._snapshot._strings[this._nameOrIndex()];
},
toString: function()
{
return "NativeHeapSnapshotEdge: " + this.name();
},
_nameOrIndex: function()
{
return this._edges.item(this.edgeIndex + this._snapshot._edgeNameOffset);
},
__proto__: WebInspector.HeapSnapshotEdge.prototype
};
WebInspector.NativeHeapSnapshotRetainerEdge = function(snapshot, retainedNodeIndex, retainerIndex)
{
WebInspector.HeapSnapshotRetainerEdge.call(this, snapshot, retainedNodeIndex, retainerIndex);
}
WebInspector.NativeHeapSnapshotRetainerEdge.prototype = {
clone: function()
{
return new WebInspector.NativeHeapSnapshotRetainerEdge(this._snapshot, this._retainedNodeIndex, this.retainerIndex());
},
isHidden: function()
{
return this._edge().isHidden();
},
isInternal: function()
{
return this._edge().isInternal();
},
isInvisible: function()
{
return this._edge().isInvisible();
},
isShortcut: function()
{
return this._edge().isShortcut();
},
isWeak: function()
{
return this._edge().isWeak();
},
__proto__: WebInspector.HeapSnapshotRetainerEdge.prototype
}
;
WebInspector.NativeMemorySnapshotView = function(profile)
{
WebInspector.View.call(this);
this.registerRequiredCSS("nativeMemoryProfiler.css");
this.element.addStyleClass("native-snapshot-view");
this._containmentDataGrid = new WebInspector.NativeSnapshotDataGrid(profile);
this._containmentDataGrid.show(this.element);
}
WebInspector.NativeMemorySnapshotView.prototype = {
__proto__: WebInspector.View.prototype
}
WebInspector.NativeSnapshotDataGrid = function(profile)
{
var columns = [
{id: "name", title: WebInspector.UIString("Object"), width: "200px", disclosure: true, sortable: true},
{id: "size", title: WebInspector.UIString("Size"), sortable: true, sort: WebInspector.DataGrid.Order.Descending},
];
WebInspector.DataGrid.call(this, columns);
this._profile = profile;
this._totalNode = new WebInspector.NativeSnapshotNode(profile._memoryBlock, profile);
if (WebInspector.settings.showNativeSnapshotUninstrumentedSize.get()) {
this.setRootNode(new WebInspector.DataGridNode(null, true));
this.rootNode().appendChild(this._totalNode)
this._totalNode.expand();
} else {
this.setRootNode(this._totalNode);
this._totalNode.populate();
}
this.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this.sortingChanged.bind(this), this);
}
WebInspector.NativeSnapshotDataGrid.prototype = {
sortingChanged: function()
{
var expandedNodes = {};
this._totalNode._storeState(expandedNodes);
this._totalNode.removeChildren();
this._totalNode._populated = false;
this._totalNode.populate();
this._totalNode._shouldRefreshChildren = true;
this._totalNode._restoreState(expandedNodes);
},
_sortingFunction: function(nodeA, nodeB)
{
var sortColumnIdentifier = this.sortColumnIdentifier();
var sortAscending = this.isSortOrderAscending();
var field1 = nodeA[sortColumnIdentifier];
var field2 = nodeB[sortColumnIdentifier];
var result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
if (!sortAscending)
result = -result;
return result;
},
__proto__: WebInspector.DataGrid.prototype
}
WebInspector.NativeSnapshotNode = function(nodeData, profile)
{
this._nodeData = nodeData;
this._profile = profile;
var viewProperties = WebInspector.MemoryBlockViewProperties._forMemoryBlock(nodeData);
var data = { name: viewProperties._description, size: this._nodeData.size };
var hasChildren = this._addChildrenFromGraph();
WebInspector.DataGridNode.call(this, data, hasChildren);
}
WebInspector.NativeSnapshotNode.prototype = {
createCell: function(columnIdentifier)
{
var cell = columnIdentifier === "size" ?
this._createSizeCell(columnIdentifier) :
WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
return cell;
},
_storeState: function(expandedNodes)
{
if (!this.expanded)
return;
expandedNodes[this.uid()] = true;
for (var i in this.children)
this.children[i]._storeState(expandedNodes);
},
_restoreState: function(expandedNodes)
{
if (!expandedNodes[this.uid()])
return;
this.expand();
for (var i in this.children)
this.children[i]._restoreState(expandedNodes);
},
uid: function()
{
if (!this._uid)
this._uid = (!this.parent || !this.parent.uid ? "" : this.parent.uid() || "") + "/" + this._nodeData.name;
return this._uid;
},
_createSizeCell: function(columnIdentifier)
{
var node = this;
var viewProperties = null;
var dimmed = false;
while (!viewProperties || viewProperties._fillStyle === "inherit") {
viewProperties = WebInspector.MemoryBlockViewProperties._forMemoryBlock(node._nodeData);
if (viewProperties._fillStyle === "inherit")
dimmed = true;
node = node.parent;
}
var sizeKB = this._nodeData.size / 1024;
var totalSize = this._profile._memoryBlock.size;
var percentage = this._nodeData.size / totalSize * 100;
var cell = document.createElement("td");
cell.className = columnIdentifier + "-column";
var textDiv = document.createElement("div");
textDiv.textContent = Number.withThousandsSeparator(sizeKB.toFixed(0)) + "\u2009" + WebInspector.UIString("KB");
textDiv.className = "size-text";
cell.appendChild(textDiv);
var barDiv = document.createElement("div");
barDiv.className = "size-bar";
barDiv.style.width = percentage + "%";
barDiv.style.backgroundColor = viewProperties._fillStyle;
var fillerDiv = document.createElement("div");
fillerDiv.className = "percent-text"
barDiv.appendChild(fillerDiv);
var percentDiv = document.createElement("div");
percentDiv.textContent = percentage.toFixed(1) + "%";
percentDiv.className = "percent-text"
barDiv.appendChild(percentDiv);
var barHolderDiv = document.createElement("div");
if (dimmed)
barHolderDiv.className = "dimmed";
barHolderDiv.appendChild(barDiv);
cell.appendChild(barHolderDiv);
return cell;
},
populate: function() {
if (this._populated)
return;
this._populated = true;
if (this._nodeData.children)
this._addChildren();
},
_addChildren: function()
{
this._nodeData.children.sort(this.dataGrid._sortingFunction.bind(this.dataGrid));
for (var node in this._nodeData.children) {
var nodeData = this._nodeData.children[node];
if (WebInspector.settings.showNativeSnapshotUninstrumentedSize.get() || nodeData.name !== "Other")
this.appendChild(new WebInspector.NativeSnapshotNode(nodeData, this._profile));
}
},
_addChildrenFromGraph: function()
{
var memoryBlock = this._nodeData;
if (memoryBlock.children)
return memoryBlock.children.length > 0;
if (memoryBlock.name === "Image") {
this._addImageDetails();
return true;
}
return false;
},
_addImageDetails: function()
{
function didLoad(proxy)
{
function didReceiveImages(result)
{
this._nodeData.children = result;
if (this.expanded)
this._addChildren();
}
proxy.images(didReceiveImages.bind(this));
}
this._profile.load(didLoad.bind(this));
},
__proto__: WebInspector.DataGridNode.prototype
}
WebInspector.MemoryAgentDispatcher = function()
{
InspectorBackend.registerMemoryDispatcher(this);
this._currentProfileHeader = null;
}
WebInspector.MemoryAgentDispatcher.instance = function()
{
if (!WebInspector.MemoryAgentDispatcher._instance)
WebInspector.MemoryAgentDispatcher._instance = new WebInspector.MemoryAgentDispatcher();
return WebInspector.MemoryAgentDispatcher._instance;
}
WebInspector.MemoryAgentDispatcher.prototype = {
addNativeSnapshotChunk: function(chunk)
{
if (this._currentProfileHeader)
this._currentProfileHeader.addNativeSnapshotChunk(chunk);
},
_onRemoveProfileHeader: function(event)
{
if (event.data === this._currentProfileHeader)
this._currentProfileHeader = null;
}
};
WebInspector.NativeProfileTypeBase = function(profileHeaderConstructor, id, name)
{
WebInspector.ProfileType.call(this, id, name);
this._profileHeaderConstructor = profileHeaderConstructor;
this._nextProfileUid = 1;
this.addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader,
WebInspector.MemoryAgentDispatcher.prototype._onRemoveProfileHeader,
WebInspector.MemoryAgentDispatcher.instance());
}
WebInspector.NativeProfileTypeBase.prototype = {
isInstantProfile: function()
{
return true;
},
buttonClicked: function()
{
if (WebInspector.MemoryAgentDispatcher.instance()._currentProfileHeader)
return false;
var profileHeader = new this._profileHeaderConstructor(this, WebInspector.UIString("Snapshot %d", this._nextProfileUid), this._nextProfileUid);
++this._nextProfileUid;
profileHeader.isTemporary = true;
this.addProfile(profileHeader);
WebInspector.MemoryAgentDispatcher.instance()._currentProfileHeader = profileHeader;
profileHeader.load(function() { });
function didReceiveMemorySnapshot(error, memoryBlock, graphMetaInformation)
{
console.assert(this === WebInspector.MemoryAgentDispatcher.instance()._currentProfileHeader);
WebInspector.MemoryAgentDispatcher.instance()._currentProfileHeader = null;
this._didReceiveMemorySnapshot(error, memoryBlock, graphMetaInformation);
}
MemoryAgent.getProcessMemoryDistribution(true, didReceiveMemorySnapshot.bind(profileHeader));
return false;
},
removeProfile: function(profile)
{
if (WebInspector.MemoryAgentDispatcher.instance()._currentProfileHeader === profile)
WebInspector.MemoryAgentDispatcher.instance()._currentProfileHeader = null;
WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
},
createTemporaryProfile: function(title)
{
title = title || WebInspector.UIString("Snapshotting\u2026");
return new this._profileHeaderConstructor(this, title);
},
createProfile: function(profile)
{
return new this._profileHeaderConstructor(this, profile.title, -1);
},
__proto__: WebInspector.ProfileType.prototype
}
WebInspector.NativeSnapshotProfileType = function()
{
WebInspector.NativeProfileTypeBase.call(this, WebInspector.NativeSnapshotProfileHeader, WebInspector.NativeSnapshotProfileType.TypeId, WebInspector.UIString("Take Native Heap Snapshot"));
}
WebInspector.NativeSnapshotProfileType.TypeId = "NATIVE_SNAPSHOT";
WebInspector.NativeSnapshotProfileType.prototype = {
get buttonTooltip()
{
return WebInspector.UIString("Capture native heap graph.");
},
get treeItemTitle()
{
return WebInspector.UIString("NATIVE SNAPSHOT");
},
get description()
{
return WebInspector.UIString("Native memory snapshot profiles show native heap graph.");
},
__proto__: WebInspector.NativeProfileTypeBase.prototype
}
WebInspector.NativeSnapshotProfileHeader = function(type, title, uid)
{
WebInspector.HeapProfileHeader.call(this, type, title, uid, 0);
this._strings = [];
this._nodes = [];
this._edges = [];
this._baseToRealNodeId = [];
}
WebInspector.NativeSnapshotProfileHeader.prototype = {
createView: function(profilesPanel)
{
return new WebInspector.NativeHeapSnapshotView(profilesPanel, this);
},
startSnapshotTransfer: function()
{
},
snapshotConstructorName: function()
{
return "NativeHeapSnapshot";
},
snapshotProxyConstructor: function()
{
return WebInspector.NativeHeapSnapshotProxy;
},
addNativeSnapshotChunk: function(chunk)
{
this._strings = this._strings.concat(chunk.strings);
this._nodes = this._nodes.concat(chunk.nodes);
this._edges = this._edges.concat(chunk.edges);
this._baseToRealNodeId = this._baseToRealNodeId.concat(chunk.baseToRealNodeId);
},
_didReceiveMemorySnapshot: function(error, memoryBlock, graphMetaInformation)
{
var metaInformation = (graphMetaInformation);
this.isTemporary = false;
var edgeFieldCount = metaInformation.edge_fields.length;
var nodeFieldCount = metaInformation.node_fields.length;
var nodeIdFieldOffset = metaInformation.node_fields.indexOf("id");
var toNodeIdFieldOffset = metaInformation.edge_fields.indexOf("to_node");
var baseToRealNodeIdMap = {};
for (var i = 0; i < this._baseToRealNodeId.length; i += 2)
baseToRealNodeIdMap[this._baseToRealNodeId[i]] = this._baseToRealNodeId[i + 1];
var nodeId2NodeIndex = {};
for (var i = nodeIdFieldOffset; i < this._nodes.length; i += nodeFieldCount)
nodeId2NodeIndex[this._nodes[i]] = i - nodeIdFieldOffset;
var edges = this._edges;
for (var i = toNodeIdFieldOffset; i < edges.length; i += edgeFieldCount) {
if (edges[i] in baseToRealNodeIdMap)
edges[i] = baseToRealNodeIdMap[edges[i]];
edges[i] = nodeId2NodeIndex[edges[i]];
}
var heapSnapshot = {
"snapshot": {
"meta": metaInformation,
node_count: this._nodes.length / nodeFieldCount,
edge_count: this._edges.length / edgeFieldCount,
root_index: this._nodes.length - nodeFieldCount
},
nodes: this._nodes,
edges: this._edges,
strings: this._strings
};
var chunk = JSON.stringify(heapSnapshot);
this.transferChunk(chunk);
this.finishHeapSnapshot();
},
__proto__: WebInspector.HeapProfileHeader.prototype
}
WebInspector.NativeHeapSnapshotView = function(parent, profile)
{
this._profile = profile;
WebInspector.HeapSnapshotView.call(this, parent, profile);
}
WebInspector.NativeHeapSnapshotView.prototype = {
get profile()
{
return this._profile;
},
__proto__: WebInspector.HeapSnapshotView.prototype
};
WebInspector.NativeMemoryProfileType = function()
{
WebInspector.NativeProfileTypeBase.call(this, WebInspector.NativeMemoryProfileHeader, WebInspector.NativeMemoryProfileType.TypeId, WebInspector.UIString("Capture Native Memory Distribution"));
}
WebInspector.NativeMemoryProfileType.TypeId = "NATIVE_MEMORY_DISTRIBUTION";
WebInspector.NativeMemoryProfileType.prototype = {
get buttonTooltip()
{
return WebInspector.UIString("Capture native memory distribution.");
},
get treeItemTitle()
{
return WebInspector.UIString("MEMORY DISTRIBUTION");
},
get description()
{
return WebInspector.UIString("Native memory snapshot profiles show memory distribution among browser subsystems.");
},
__proto__: WebInspector.NativeProfileTypeBase.prototype
}
WebInspector.NativeMemoryProfileHeader = function(type, title, uid)
{
WebInspector.NativeSnapshotProfileHeader.call(this, type, title, uid);
this._memoryBlock = null;
}
WebInspector.NativeMemoryProfileHeader.prototype = {
createSidebarTreeElement: function()
{
return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
},
createView: function(profilesPanel)
{
return new WebInspector.NativeMemorySnapshotView(this);
},
_updateSnapshotStatus: function()
{
WebInspector.NativeSnapshotProfileHeader.prototype._updateSnapshotStatus.call(this);
this.sidebarElement.subtitle = Number.bytesToString( (this._memoryBlock.size));
},
_didReceiveMemorySnapshot: function(error, memoryBlock, graphMetaInformation)
{
WebInspector.NativeSnapshotProfileHeader.prototype._didReceiveMemorySnapshot.call(this, error, memoryBlock, graphMetaInformation);
if (memoryBlock.size && memoryBlock.children) {
var knownSize = 0;
for (var i = 0; i < memoryBlock.children.length; i++) {
var size = memoryBlock.children[i].size;
if (size)
knownSize += size;
}
var otherSize = memoryBlock.size - knownSize;
if (otherSize) {
memoryBlock.children.push({
name: "Other",
size: otherSize
});
}
}
this._memoryBlock = memoryBlock;
},
__proto__: WebInspector.NativeSnapshotProfileHeader.prototype
}
WebInspector.MemoryBlockViewProperties = function(fillStyle, name, description)
{
this._fillStyle = fillStyle;
this._name = name;
this._description = description;
}
WebInspector.MemoryBlockViewProperties._standardBlocks = null;
WebInspector.MemoryBlockViewProperties._initialize = function()
{
if (WebInspector.MemoryBlockViewProperties._standardBlocks)
return;
WebInspector.MemoryBlockViewProperties._standardBlocks = {};
function addBlock(fillStyle, name, description)
{
WebInspector.MemoryBlockViewProperties._standardBlocks[name] = new WebInspector.MemoryBlockViewProperties(fillStyle, name, WebInspector.UIString(description));
}
addBlock("hsl( 0, 0%, 60%)", "ProcessPrivateMemory", "Total");
addBlock("hsl( 0, 0%, 80%)", "OwnersTypePlaceholder", "OwnersTypePlaceholder");
addBlock("hsl( 0, 0%, 60%)", "Other", "Other");
addBlock("hsl(220, 80%, 70%)", "Image", "Images");
addBlock("hsl(100, 60%, 50%)", "JSHeap", "JavaScript heap");
addBlock("hsl( 90, 40%, 80%)", "JSExternalResources", "JavaScript external resources");
addBlock("hsl( 90, 60%, 80%)", "CSS", "CSS");
addBlock("hsl( 0, 50%, 60%)", "DOM", "DOM");
addBlock("hsl( 0, 80%, 60%)", "WebInspector", "Inspector data");
addBlock("hsl( 36, 90%, 50%)", "Resources", "Resources");
addBlock("hsl( 40, 80%, 80%)", "GlyphCache", "Glyph cache resources");
addBlock("hsl( 35, 80%, 80%)", "DOMStorageCache", "DOM storage cache");
addBlock("hsl( 60, 80%, 60%)", "RenderTree", "Render tree");
addBlock("hsl( 20, 80%, 50%)", "MallocWaste", "Memory allocator waste");
}
WebInspector.MemoryBlockViewProperties._forMemoryBlock = function(memoryBlock)
{
WebInspector.MemoryBlockViewProperties._initialize();
var result = WebInspector.MemoryBlockViewProperties._standardBlocks[memoryBlock.name];
if (result)
return result;
return new WebInspector.MemoryBlockViewProperties("inherit", memoryBlock.name, memoryBlock.name);
}
;
WebInspector.ProfileLauncherView = function(profilesPanel)
{
WebInspector.View.call(this);
this._panel = profilesPanel;
this.element.addStyleClass("profile-launcher-view");
this.element.addStyleClass("panel-enabler-view");
this._contentElement = this.element.createChild("div", "profile-launcher-view-content");
this._innerContentElement = this._contentElement.createChild("div");
this._controlButton = this._contentElement.createChild("button", "control-profiling");
this._controlButton.addEventListener("click", this._controlButtonClicked.bind(this), false);
}
WebInspector.ProfileLauncherView.prototype = {
addProfileType: function(profileType)
{
var descriptionElement = this._innerContentElement.createChild("h1");
descriptionElement.textContent = profileType.description;
var decorationElement = profileType.decorationElement();
if (decorationElement)
this._innerContentElement.appendChild(decorationElement);
this._isInstantProfile = profileType.isInstantProfile();
},
_controlButtonClicked: function()
{
this._panel.toggleRecordButton();
},
_updateControls: function()
{
if (this._isInstantProfile) {
this._controlButton.removeStyleClass("running");
this._controlButton.textContent = WebInspector.UIString("Take Snapshot");
} else if (this._isProfiling) {
this._controlButton.addStyleClass("running");
this._controlButton.textContent = WebInspector.UIString("Stop");
} else {
this._controlButton.removeStyleClass("running");
this._controlButton.textContent = WebInspector.UIString("Start");
}
},
profileStarted: function()
{
this._isProfiling = true;
this._updateControls();
},
profileFinished: function()
{
this._isProfiling = false;
this._updateControls();
},
__proto__: WebInspector.View.prototype
}
WebInspector.MultiProfileLauncherView = function(profilesPanel)
{
WebInspector.ProfileLauncherView.call(this, profilesPanel);
var header = this._innerContentElement.createChild("h1");
header.textContent = WebInspector.UIString("Select profiling type");
this._profileTypeSelectorForm = this._innerContentElement.createChild("form");
this._innerContentElement.createChild("div", "flexible-space");
}
WebInspector.MultiProfileLauncherView.EventTypes = {
ProfileTypeSelected: "profile-type-selected"
}
WebInspector.MultiProfileLauncherView.prototype = {
addProfileType: function(profileType)
{
var checked = !this._profileTypeSelectorForm.children.length;
var labelElement = this._profileTypeSelectorForm.createChild("label");
labelElement.textContent = profileType.name;
var optionElement = document.createElement("input");
labelElement.insertBefore(optionElement, labelElement.firstChild);
optionElement.type = "radio";
optionElement.name = "profile-type";
optionElement.style.hidden = true;
if (checked) {
optionElement.checked = checked;
this.dispatchEventToListeners(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, profileType);
}
optionElement.addEventListener("change", this._profileTypeChanged.bind(this, profileType), false);
var descriptionElement = labelElement.createChild("p");
descriptionElement.textContent = profileType.description;
var decorationElement = profileType.decorationElement();
if (decorationElement)
labelElement.appendChild(decorationElement);
},
_controlButtonClicked: function()
{
this._panel.toggleRecordButton();
},
_updateControls: function()
{
WebInspector.ProfileLauncherView.prototype._updateControls.call(this);
var items = this._profileTypeSelectorForm.elements;
for (var i = 0; i < items.length; ++i) {
if (items[i].type === "radio")
items[i].disabled = this._isProfiling;
}
},
_profileTypeChanged: function(profileType, event)
{
this.dispatchEventToListeners(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, profileType);
this._isInstantProfile = profileType.isInstantProfile();
this._updateControls();
},
profileStarted: function()
{
this._isProfiling = true;
this._updateControls();
},
profileFinished: function()
{
this._isProfiling = false;
this._updateControls();
},
__proto__: WebInspector.ProfileLauncherView.prototype
}
;
WebInspector.TopDownProfileDataGridNode = function(profileNode, owningTree)
{
var hasChildren = !!(profileNode.children && profileNode.children.length);
WebInspector.ProfileDataGridNode.call(this, profileNode, owningTree, hasChildren);
this._remainingChildren = profileNode.children;
}
WebInspector.TopDownProfileDataGridNode.prototype = {
_sharedPopulate: function()
{
var children = this._remainingChildren;
var childrenLength = children.length;
for (var i = 0; i < childrenLength; ++i)
this.appendChild(new WebInspector.TopDownProfileDataGridNode(children[i], this.tree));
this._remainingChildren = null;
},
_exclude: function(aCallUID)
{
if (this._remainingChildren)
this.populate();
this._save();
var children = this.children;
var index = this.children.length;
while (index--)
children[index]._exclude(aCallUID);
var child = this.childrenByCallUID[aCallUID];
if (child)
this._merge(child, true);
},
__proto__: WebInspector.ProfileDataGridNode.prototype
}
WebInspector.TopDownProfileDataGridTree = function(profileView, rootProfileNode)
{
WebInspector.ProfileDataGridTree.call(this, profileView, rootProfileNode);
this._remainingChildren = rootProfileNode.children;
var any = (this);
var node = (any);
WebInspector.TopDownProfileDataGridNode.prototype.populate.call(node);
}
WebInspector.TopDownProfileDataGridTree.prototype = {
focus: function(profileDataGridNode)
{
if (!profileDataGridNode)
return;
this._save();
profileDataGridNode.savePosition();
this.children = [profileDataGridNode];
this.totalTime = profileDataGridNode.totalTime;
},
exclude: function(profileDataGridNode)
{
if (!profileDataGridNode)
return;
this._save();
var excludedCallUID = profileDataGridNode.callUID;
var any = (this);
var node = (any);
WebInspector.TopDownProfileDataGridNode.prototype._exclude.call(node, excludedCallUID);
if (this.lastComparator)
this.sort(this.lastComparator, true);
},
restore: function()
{
if (!this._savedChildren)
return;
this.children[0].restorePosition();
WebInspector.ProfileDataGridTree.prototype.restore.call(this);
},
_merge: WebInspector.TopDownProfileDataGridNode.prototype._merge,
_sharedPopulate: WebInspector.TopDownProfileDataGridNode.prototype._sharedPopulate,
__proto__: WebInspector.ProfileDataGridTree.prototype
}
;
WebInspector.CanvasProfileView = function(profile)
{
WebInspector.View.call(this);
this.registerRequiredCSS("canvasProfiler.css");
this._profile = profile;
this._traceLogId = profile.traceLogId();
this.element.addStyleClass("canvas-profile-view");
this._linkifier = new WebInspector.Linkifier();
this._splitView = new WebInspector.SplitView(false, "canvasProfileViewSplitLocation", 300);
var replayImageContainer = this._splitView.firstElement();
replayImageContainer.id = "canvas-replay-image-container";
this._replayImageElement = replayImageContainer.createChild("image", "canvas-replay-image");
this._debugInfoElement = replayImageContainer.createChild("div", "canvas-debug-info hidden");
this._spinnerIcon = replayImageContainer.createChild("img", "canvas-spinner-icon hidden");
var replayInfoContainer = this._splitView.secondElement();
var controlsContainer = replayInfoContainer.createChild("div", "status-bar");
var logGridContainer = replayInfoContainer.createChild("div", "canvas-replay-log");
this._createControlButton(controlsContainer, "canvas-replay-first-step", WebInspector.UIString("First call."), this._onReplayFirstStepClick.bind(this));
this._createControlButton(controlsContainer, "canvas-replay-prev-step", WebInspector.UIString("Previous call."), this._onReplayStepClick.bind(this, false));
this._createControlButton(controlsContainer, "canvas-replay-next-step", WebInspector.UIString("Next call."), this._onReplayStepClick.bind(this, true));
this._createControlButton(controlsContainer, "canvas-replay-prev-draw", WebInspector.UIString("Previous drawing call."), this._onReplayDrawingCallClick.bind(this, false));
this._createControlButton(controlsContainer, "canvas-replay-next-draw", WebInspector.UIString("Next drawing call."), this._onReplayDrawingCallClick.bind(this, true));
this._createControlButton(controlsContainer, "canvas-replay-last-step", WebInspector.UIString("Last call."), this._onReplayLastStepClick.bind(this));
this._replayContextSelector = new WebInspector.StatusBarComboBox(this._onReplayContextChanged.bind(this));
this._replayContextSelector.createOption("<screenshot auto>", WebInspector.UIString("Show screenshot of the last replayed resource."), "");
controlsContainer.appendChild(this._replayContextSelector.element);
this._replayContexts = {};
this._currentResourceStates = {};
var columns = [
{title: "#", sortable: true, width: "5%"},
{title: WebInspector.UIString("Call"), sortable: true, width: "75%", disclosure: true},
{title: WebInspector.UIString("Location"), sortable: true, width: "20%"}
];
this._logGrid = new WebInspector.DataGrid(columns);
this._logGrid.element.addStyleClass("fill");
this._logGrid.show(logGridContainer);
this._logGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._replayTraceLog.bind(this));
this._splitView.show(this.element);
this._requestTraceLog(0);
}
WebInspector.CanvasProfileView.TraceLogPollingInterval = 500;
WebInspector.CanvasProfileView.prototype = {
dispose: function()
{
this._linkifier.reset();
},
get statusBarItems()
{
return [];
},
get profile()
{
return this._profile;
},
elementsToRestoreScrollPositionsFor: function()
{
return [this._logGrid.scrollContainer];
},
_createControlButton: function(parent, className, title, clickCallback)
{
var button = new WebInspector.StatusBarButton(title, className);
button.element.addEventListener("click", clickCallback, false);
parent.appendChild(button.element);
},
_onReplayContextChanged: function()
{
function didReceiveResourceState(error, resourceState)
{
this._enableWaitIcon(false);
if (error)
return;
this._currentResourceStates[resourceState.id] = resourceState;
var selectedContextId = this._replayContextSelector.selectedOption().value;
if (selectedContextId === resourceState.id)
this._replayImageElement.src = resourceState.imageURL;
}
var selectedContextId = this._replayContextSelector.selectedOption().value || "auto";
var resourceState = this._currentResourceStates[selectedContextId];
if (resourceState)
this._replayImageElement.src = resourceState.imageURL;
else {
this._enableWaitIcon(true);
this._replayImageElement.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
CanvasAgent.getResourceState(this._traceLogId, selectedContextId, didReceiveResourceState.bind(this));
}
},
_onReplayStepClick: function(forward)
{
var selectedNode = this._logGrid.selectedNode;
if (!selectedNode)
return;
var nextNode = forward ? selectedNode.traverseNextNode(false) : selectedNode.traversePreviousNode(false);
(nextNode || selectedNode).revealAndSelect();
},
_onReplayDrawingCallClick: function(forward)
{
var selectedNode = this._logGrid.selectedNode;
if (!selectedNode)
return;
var nextNode = selectedNode;
while (nextNode) {
var sibling = forward ? nextNode.nextSibling : nextNode.previousSibling;
if (sibling) {
nextNode = sibling;
if (nextNode.hasChildren || nextNode.call.isDrawingCall)
break;
} else {
nextNode = nextNode.parent;
if (!forward)
break;
}
}
if (!nextNode && forward)
this._onReplayLastStepClick();
else
(nextNode || selectedNode).revealAndSelect();
},
_onReplayFirstStepClick: function()
{
var firstNode = this._logGrid.rootNode().children[0];
if (firstNode)
firstNode.revealAndSelect();
},
_onReplayLastStepClick: function()
{
var lastNode = this._logGrid.rootNode().children.peekLast();
if (!lastNode)
return;
while (lastNode.expanded) {
var lastChild = lastNode.children.peekLast();
if (!lastChild)
break;
lastNode = lastChild;
}
lastNode.revealAndSelect();
},
_enableWaitIcon: function(enable)
{
this._spinnerIcon.enableStyleClass("hidden", !enable);
this._debugInfoElement.enableStyleClass("hidden", enable);
},
_replayTraceLog: function()
{
if (this._pendingReplayTraceLogEvent)
return;
var index = this._selectedCallIndex();
if (index === -1 || index === this._lastReplayCallIndex)
return;
this._lastReplayCallIndex = index;
this._pendingReplayTraceLogEvent = true;
var time = Date.now();
function didReplayTraceLog(error, resourceState)
{
delete this._pendingReplayTraceLogEvent;
if (index !== this._selectedCallIndex()) {
this._replayTraceLog();
return;
}
this._enableWaitIcon(false);
if (error)
return;
this._currentResourceStates = {};
this._currentResourceStates["auto"] = resourceState;
this._currentResourceStates[resourceState.id] = resourceState;
this._debugInfoElement.textContent = "Replay time: " + (Date.now() - time) + "ms";
this._onReplayContextChanged();
}
this._enableWaitIcon(true);
CanvasAgent.replayTraceLog(this._traceLogId, index, didReplayTraceLog.bind(this));
},
_didReceiveTraceLog: function(error, traceLog)
{
this._enableWaitIcon(false);
if (error || !traceLog)
return;
var callNodes = [];
var calls = traceLog.calls;
var index = traceLog.startOffset;
for (var i = 0, n = calls.length; i < n; ++i) {
var call = calls[i];
this._requestReplayContextInfo(call.contextId);
var gridNode = this._createCallNode(index++, call);
callNodes.push(gridNode);
}
this._appendCallNodes(callNodes);
if (traceLog.alive)
setTimeout(this._requestTraceLog.bind(this, index), WebInspector.CanvasProfileView.TraceLogPollingInterval);
else
this._flattenSingleFrameNode();
this._profile._updateCapturingStatus(traceLog);
this._onReplayLastStepClick();
},
_requestTraceLog: function(offset)
{
this._enableWaitIcon(true);
CanvasAgent.getTraceLog(this._traceLogId, offset, undefined, this._didReceiveTraceLog.bind(this));
},
_requestReplayContextInfo: function(contextId)
{
if (this._replayContexts[contextId])
return;
this._replayContexts[contextId] = true;
function didReceiveResourceInfo(error, resourceInfo)
{
if (error) {
delete this._replayContexts[contextId];
return;
}
this._replayContextSelector.createOption(resourceInfo.description, WebInspector.UIString("Show screenshot of this context's canvas."), contextId);
}
CanvasAgent.getResourceInfo(contextId, didReceiveResourceInfo.bind(this));
},
_selectedCallIndex: function()
{
var node = this._logGrid.selectedNode;
return node ? this._peekLastRecursively(node).index : -1;
},
_peekLastRecursively: function(node)
{
var lastChild;
while ((lastChild = node.children.peekLast()))
node = (lastChild);
return node;
},
_appendCallNodes: function(callNodes)
{
var rootNode = this._logGrid.rootNode();
var frameNode = (rootNode.children.peekLast());
if (frameNode && this._peekLastRecursively(frameNode).call.isFrameEndCall)
frameNode = null;
for (var i = 0, n = callNodes.length; i < n; ++i) {
if (!frameNode) {
var index = rootNode.children.length;
var data = {};
data[0] = "";
data[1] = "Frame #" + (index + 1);
data[2] = "";
frameNode = new WebInspector.DataGridNode(data);
frameNode.selectable = true;
rootNode.appendChild(frameNode);
}
var nextFrameCallIndex = i + 1;
while (nextFrameCallIndex < n && !callNodes[nextFrameCallIndex - 1].call.isFrameEndCall)
++nextFrameCallIndex;
this._appendCallNodesToFrameNode(frameNode, callNodes, i, nextFrameCallIndex);
i = nextFrameCallIndex - 1;
frameNode = null;
}
},
_appendCallNodesToFrameNode: function(frameNode, callNodes, fromIndex, toIndex)
{
var self = this;
function appendDrawCallGroup()
{
var index = self._drawCallGroupsCount || 0;
var data = {};
data[0] = "";
data[1] = "Draw call group #" + (index + 1);
data[2] = "";
var node = new WebInspector.DataGridNode(data);
node.selectable = true;
self._drawCallGroupsCount = index + 1;
frameNode.appendChild(node);
return node;
}
function splitDrawCallGroup(drawCallGroup)
{
var splitIndex = 0;
var splitNode;
while ((splitNode = drawCallGroup.children[splitIndex])) {
if (splitNode.call.isDrawingCall)
break;
++splitIndex;
}
var newDrawCallGroup = appendDrawCallGroup();
var lastNode;
while ((lastNode = drawCallGroup.children[splitIndex + 1]))
newDrawCallGroup.appendChild(lastNode);
return newDrawCallGroup;
}
var drawCallGroup = frameNode.children.peekLast();
var groupHasDrawCall = false;
if (drawCallGroup) {
for (var i = 0, n = drawCallGroup.children.length; i < n; ++i) {
if (drawCallGroup.children[i].call.isDrawingCall) {
groupHasDrawCall = true;
break;
}
}
} else
drawCallGroup = appendDrawCallGroup();
for (var i = fromIndex; i < toIndex; ++i) {
var node = callNodes[i];
drawCallGroup.appendChild(node);
if (node.call.isDrawingCall) {
if (groupHasDrawCall)
drawCallGroup = splitDrawCallGroup(drawCallGroup);
else
groupHasDrawCall = true;
}
}
},
_createCallNode: function(index, call)
{
var data = {};
data[0] = index + 1;
data[1] = call.functionName || "context." + call.property;
data[2] = "";
if (call.sourceURL) {
var lineNumber = Math.max(0, call.lineNumber - 1) || 0;
var columnNumber = Math.max(0, call.columnNumber - 1) || 0;
data[2] = this._linkifier.linkifyLocation(call.sourceURL, lineNumber, columnNumber);
}
if (call.arguments) {
var args = call.arguments.map(function(argument) {
return argument.description;
});
data[1] += "(" + args.join(", ") + ")";
} else
data[1] += " = " + call.value.description;
if (typeof call.result !== "undefined")
data[1] += " => " + call.result.description;
var node = new WebInspector.DataGridNode(data);
node.index = index;
node.selectable = true;
node.call = call;
return node;
},
_flattenSingleFrameNode: function()
{
var rootNode = this._logGrid.rootNode();
if (rootNode.children.length !== 1)
return;
var frameNode = rootNode.children[0];
while (frameNode.children[0])
rootNode.appendChild(frameNode.children[0]);
rootNode.removeChild(frameNode);
},
__proto__: WebInspector.View.prototype
}
WebInspector.CanvasProfileType = function()
{
WebInspector.ProfileType.call(this, WebInspector.CanvasProfileType.TypeId, WebInspector.UIString("Capture Canvas Frame"));
this._nextProfileUid = 1;
this._recording = false;
this._lastProfileHeader = null;
this._capturingModeSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
this._capturingModeSelector.element.title = WebInspector.UIString("Canvas capture mode.");
this._capturingModeSelector.createOption(WebInspector.UIString("Single Frame"), WebInspector.UIString("Capture a single canvas frame."), "");
this._capturingModeSelector.createOption(WebInspector.UIString("Consecutive Frames"), WebInspector.UIString("Capture consecutive canvas frames."), "1");
this._frameOptions = {};
this._framesWithCanvases = {};
this._frameSelector = new WebInspector.StatusBarComboBox(this._dispatchViewUpdatedEvent.bind(this));
this._frameSelector.element.title = WebInspector.UIString("Frame containing the canvases to capture.");
this._frameSelector.element.addStyleClass("hidden");
WebInspector.runtimeModel.contextLists().forEach(this._addFrame, this);
WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListAdded, this._frameAdded, this);
WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListRemoved, this._frameRemoved, this);
this._decorationElement = document.createElement("div");
this._decorationElement.addStyleClass("profile-canvas-decoration");
this._decorationElement.addStyleClass("hidden");
this._decorationElement.textContent = WebInspector.UIString("There is an uninstrumented canvas on the page. Reload the page to instrument it.");
var reloadPageButton = this._decorationElement.createChild("button");
reloadPageButton.type = "button";
reloadPageButton.textContent = WebInspector.UIString("Reload");
reloadPageButton.addEventListener("click", this._onReloadPageButtonClick.bind(this), false);
this._dispatcher = new WebInspector.CanvasDispatcher(this);
CanvasAgent.enable(this._updateDecorationElement.bind(this));
WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._updateDecorationElement, this);
}
WebInspector.CanvasProfileType.TypeId = "CANVAS_PROFILE";
WebInspector.CanvasProfileType.prototype = {
get statusBarItems()
{
return [this._capturingModeSelector.element, this._frameSelector.element];
},
get buttonTooltip()
{
if (this._isSingleFrameMode())
return WebInspector.UIString("Capture next canvas frame.");
else
return this._recording ? WebInspector.UIString("Stop capturing canvas frames.") : WebInspector.UIString("Start capturing canvas frames.");
},
buttonClicked: function()
{
if (this._recording) {
this._recording = false;
this._stopFrameCapturing();
} else if (this._isSingleFrameMode()) {
this._recording = false;
this._runSingleFrameCapturing();
} else {
this._recording = true;
this._startFrameCapturing();
}
return this._recording;
},
_runSingleFrameCapturing: function()
{
var frameId = this._selectedFrameId();
CanvasAgent.captureFrame(frameId, this._didStartCapturingFrame.bind(this, frameId));
},
_startFrameCapturing: function()
{
var frameId = this._selectedFrameId();
CanvasAgent.startCapturing(frameId, this._didStartCapturingFrame.bind(this, frameId));
},
_stopFrameCapturing: function()
{
if (!this._lastProfileHeader)
return;
var profileHeader = this._lastProfileHeader;
var traceLogId = profileHeader.traceLogId();
this._lastProfileHeader = null;
function didStopCapturing()
{
profileHeader._updateCapturingStatus();
}
CanvasAgent.stopCapturing(traceLogId, didStopCapturing.bind(this));
},
_didStartCapturingFrame: function(frameId, error, traceLogId)
{
if (error || this._lastProfileHeader && this._lastProfileHeader.traceLogId() === traceLogId)
return;
var profileHeader = new WebInspector.CanvasProfileHeader(this, WebInspector.UIString("Trace Log %d", this._nextProfileUid), this._nextProfileUid, traceLogId, frameId);
++this._nextProfileUid;
this._lastProfileHeader = profileHeader;
this.addProfile(profileHeader);
profileHeader._updateCapturingStatus();
},
get treeItemTitle()
{
return WebInspector.UIString("CANVAS PROFILE");
},
get description()
{
return WebInspector.UIString("Canvas calls instrumentation");
},
decorationElement: function()
{
return this._decorationElement;
},
_reset: function()
{
WebInspector.ProfileType.prototype._reset.call(this);
this._nextProfileUid = 1;
},
removeProfile: function(profile)
{
WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
if (this._recording && profile === this._lastProfileHeader)
this._recording = false;
},
setRecordingProfile: function(isProfiling)
{
this._recording = isProfiling;
},
createTemporaryProfile: function(title)
{
title = title || WebInspector.UIString("Capturing\u2026");
return new WebInspector.CanvasProfileHeader(this, title);
},
createProfile: function(profile)
{
return new WebInspector.CanvasProfileHeader(this, profile.title, -1);
},
_updateDecorationElement: function()
{
function callback(error, result)
{
var hideWarning = (error || !result);
this._decorationElement.enableStyleClass("hidden", hideWarning);
}
CanvasAgent.hasUninstrumentedCanvases(callback.bind(this));
},
_onReloadPageButtonClick: function(event)
{
PageAgent.reload(event.shiftKey);
},
_isSingleFrameMode: function()
{
return !this._capturingModeSelector.selectedOption().value;
},
_frameAdded: function(event)
{
var contextList = (event.data);
this._addFrame(contextList);
},
_addFrame: function(contextList)
{
var frameId = contextList.frameId;
var option = document.createElement("option");
option.text = contextList.displayName;
option.title = contextList.url;
option.value = frameId;
this._frameOptions[frameId] = option;
if (this._framesWithCanvases[frameId]) {
this._frameSelector.addOption(option);
this._dispatchViewUpdatedEvent();
}
},
_frameRemoved: function(event)
{
var contextList = (event.data);
var frameId = contextList.frameId;
var option = this._frameOptions[frameId];
if (option && this._framesWithCanvases[frameId]) {
this._frameSelector.removeOption(option);
this._dispatchViewUpdatedEvent();
}
delete this._frameOptions[frameId];
delete this._framesWithCanvases[frameId];
},
_contextCreated: function(frameId)
{
if (this._framesWithCanvases[frameId])
return;
this._framesWithCanvases[frameId] = true;
var option = this._frameOptions[frameId];
if (option) {
this._frameSelector.addOption(option);
this._dispatchViewUpdatedEvent();
}
},
_traceLogsRemoved: function(frameId, traceLogId)
{
var sidebarElementsToDelete = [];
var sidebarElements = ((this.treeElement && this.treeElement.children) || []);
for (var i = 0, n = sidebarElements.length; i < n; ++i) {
var header = (sidebarElements[i].profile);
if (!header)
continue;
if (frameId && frameId !== header.frameId())
continue;
if (traceLogId && traceLogId !== header.traceLogId())
continue;
sidebarElementsToDelete.push(sidebarElements[i]);
}
for (var i = 0, n = sidebarElementsToDelete.length; i < n; ++i)
sidebarElementsToDelete[i].ondelete();
},
_selectedFrameId: function()
{
var option = this._frameSelector.selectedOption();
return option ? option.value : undefined;
},
_dispatchViewUpdatedEvent: function()
{
this._frameSelector.element.enableStyleClass("hidden", this._frameSelector.size() <= 1);
this.dispatchEventToListeners(WebInspector.ProfileType.Events.ViewUpdated);
},
__proto__: WebInspector.ProfileType.prototype
}
WebInspector.CanvasDispatcher = function(profileType)
{
this._profileType = profileType;
InspectorBackend.registerCanvasDispatcher(this);
}
WebInspector.CanvasDispatcher.prototype = {
contextCreated: function(frameId)
{
this._profileType._contextCreated(frameId);
},
traceLogsRemoved: function(frameId, traceLogId)
{
this._profileType._traceLogsRemoved(frameId, traceLogId);
}
}
WebInspector.CanvasProfileHeader = function(type, title, uid, traceLogId, frameId)
{
WebInspector.ProfileHeader.call(this, type, title, uid);
this._traceLogId = traceLogId || "";
this._frameId = frameId;
this._alive = true;
this._traceLogSize = 0;
}
WebInspector.CanvasProfileHeader.prototype = {
traceLogId: function()
{
return this._traceLogId;
},
frameId: function()
{
return this._frameId;
},
createSidebarTreeElement: function()
{
return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Trace Log %d"), "profile-sidebar-tree-item");
},
createView: function(profilesPanel)
{
return new WebInspector.CanvasProfileView(this);
},
dispose: function()
{
if (this._traceLogId) {
CanvasAgent.dropTraceLog(this._traceLogId);
clearTimeout(this._requestStatusTimer);
this._alive = false;
}
},
_updateCapturingStatus: function(traceLog)
{
if (!this.sidebarElement || !this._traceLogId)
return;
if (traceLog) {
this._alive = traceLog.alive;
this._traceLogSize = traceLog.totalAvailableCalls;
}
this.sidebarElement.subtitle = this._alive ? WebInspector.UIString("Capturing\u2026 %d calls", this._traceLogSize) : WebInspector.UIString("Captured %d calls", this._traceLogSize);
this.sidebarElement.wait = this._alive;
if (this._alive) {
clearTimeout(this._requestStatusTimer);
this._requestStatusTimer = setTimeout(this._requestCapturingStatus.bind(this), WebInspector.CanvasProfileView.TraceLogPollingInterval);
}
},
_requestCapturingStatus: function()
{
function didReceiveTraceLog(error, traceLog)
{
if (error)
return;
this._alive = traceLog.alive;
this._traceLogSize = traceLog.totalAvailableCalls;
this._updateCapturingStatus();
}
CanvasAgent.getTraceLog(this._traceLogId, 0, 0, didReceiveTraceLog.bind(this));
},
__proto__: WebInspector.ProfileHeader.prototype
}
;