mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-10 13:44:39 +08:00
Move and rename files. NOAUTOREVERT=true NOPRESUBMIT=true NOTREECHECKS=true Bug: 768828 TBR=darin@chromium.org NOTRY=true Change-Id: I66d3b155808bc5bdbf237b80208e1e552bcf7f28 Reviewed-on: https://chromium-review.googlesource.com/1001153 Reviewed-by: Blink Reformat <blink-reformat@chromium.org> Commit-Queue: Blink Reformat <blink-reformat@chromium.org> Cr-Original-Commit-Position: refs/heads/master@{#549061} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: 0aee4434a4dba42a42abaea9bfbc0cd196a63bc1
847 lines
26 KiB
JavaScript
847 lines
26 KiB
JavaScript
/*
|
|
* Copyright (C) 2008 Apple Inc. All Rights Reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/**
|
|
* @implements {Profiler.ProfileType.DataDisplayDelegate}
|
|
* @unrestricted
|
|
*/
|
|
Profiler.ProfilesPanel = class extends UI.PanelWithSidebar {
|
|
/**
|
|
* @param {string} name
|
|
* @param {!Array.<!Profiler.ProfileType>} profileTypes
|
|
* @param {string} recordingActionId
|
|
*/
|
|
constructor(name, profileTypes, recordingActionId) {
|
|
super(name);
|
|
this._profileTypes = profileTypes;
|
|
this.registerRequiredCSS('profiler/heapProfiler.css');
|
|
this.registerRequiredCSS('profiler/profilesPanel.css');
|
|
this.registerRequiredCSS('object_ui/objectValue.css');
|
|
|
|
const mainContainer = new UI.VBox();
|
|
this.splitWidget().setMainWidget(mainContainer);
|
|
|
|
this.profilesItemTreeElement = new Profiler.ProfilesSidebarTreeElement(this);
|
|
|
|
this._sidebarTree = new UI.TreeOutlineInShadow();
|
|
this._sidebarTree.registerRequiredCSS('profiler/profilesSidebarTree.css');
|
|
this._sidebarTree.element.classList.add('profiles-sidebar-tree-box');
|
|
this.panelSidebarElement().appendChild(this._sidebarTree.element);
|
|
|
|
this._sidebarTree.appendChild(this.profilesItemTreeElement);
|
|
|
|
this.profileViews = createElement('div');
|
|
this.profileViews.id = 'profile-views';
|
|
this.profileViews.classList.add('vbox');
|
|
mainContainer.element.appendChild(this.profileViews);
|
|
|
|
this._toolbarElement = createElementWithClass('div', 'profiles-toolbar');
|
|
mainContainer.element.insertBefore(this._toolbarElement, mainContainer.element.firstChild);
|
|
|
|
this.panelSidebarElement().classList.add('profiles-tree-sidebar');
|
|
const toolbarContainerLeft = createElementWithClass('div', 'profiles-toolbar');
|
|
this.panelSidebarElement().insertBefore(toolbarContainerLeft, this.panelSidebarElement().firstChild);
|
|
const toolbar = new UI.Toolbar('', toolbarContainerLeft);
|
|
|
|
this._toggleRecordAction =
|
|
/** @type {!UI.Action }*/ (UI.actionRegistry.action(recordingActionId));
|
|
this._toggleRecordButton = UI.Toolbar.createActionButton(this._toggleRecordAction);
|
|
toolbar.appendToolbarItem(this._toggleRecordButton);
|
|
|
|
this.clearResultsButton = new UI.ToolbarButton(Common.UIString('Clear all profiles'), 'largeicon-clear');
|
|
this.clearResultsButton.addEventListener(UI.ToolbarButton.Events.Click, this._reset, this);
|
|
toolbar.appendToolbarItem(this.clearResultsButton);
|
|
toolbar.appendSeparator();
|
|
toolbar.appendToolbarItem(UI.Toolbar.createActionButtonForId('components.collect-garbage'));
|
|
|
|
this._profileViewToolbar = new UI.Toolbar('', this._toolbarElement);
|
|
|
|
this._profileGroups = {};
|
|
this._launcherView = new Profiler.ProfileLauncherView(this);
|
|
this._launcherView.addEventListener(
|
|
Profiler.ProfileLauncherView.Events.ProfileTypeSelected, this._onProfileTypeSelected, this);
|
|
|
|
this._profileToView = [];
|
|
this._typeIdToSidebarSection = {};
|
|
const types = this._profileTypes;
|
|
for (let i = 0; i < types.length; i++)
|
|
this._registerProfileType(types[i]);
|
|
this._launcherView.restoreSelectedProfileType();
|
|
this.profilesItemTreeElement.select();
|
|
this._showLauncherView();
|
|
|
|
this._createFileSelectorElement();
|
|
this.element.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), false);
|
|
|
|
this.contentElement.addEventListener('keydown', this._onKeyDown.bind(this), false);
|
|
|
|
SDK.targetManager.addEventListener(SDK.TargetManager.Events.SuspendStateChanged, this._onSuspendStateChanged, this);
|
|
}
|
|
|
|
/**
|
|
* @param {!Event} event
|
|
*/
|
|
_onKeyDown(event) {
|
|
let handled = false;
|
|
if (event.key === 'ArrowDown' && !event.altKey)
|
|
handled = this._sidebarTree.selectNext();
|
|
else if (event.key === 'ArrowUp' && !event.altKey)
|
|
handled = this._sidebarTree.selectPrevious();
|
|
if (handled)
|
|
event.consume(true);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {?UI.SearchableView}
|
|
*/
|
|
searchableView() {
|
|
return this.visibleView && this.visibleView.searchableView ? this.visibleView.searchableView() : null;
|
|
}
|
|
|
|
_createFileSelectorElement() {
|
|
if (this._fileSelectorElement)
|
|
this.element.removeChild(this._fileSelectorElement);
|
|
this._fileSelectorElement = UI.createFileSelectorElement(this._loadFromFile.bind(this));
|
|
Profiler.ProfilesPanel._fileSelectorElement = this._fileSelectorElement;
|
|
this.element.appendChild(this._fileSelectorElement);
|
|
}
|
|
|
|
/**
|
|
* @param {string} fileName
|
|
* @return {?Profiler.ProfileType}
|
|
*/
|
|
_findProfileTypeByExtension(fileName) {
|
|
return this._profileTypes.find(type => !!type.fileExtension() && fileName.endsWith(type.fileExtension() || '')) ||
|
|
null;
|
|
}
|
|
|
|
/**
|
|
* @param {!File} file
|
|
*/
|
|
async _loadFromFile(file) {
|
|
this._createFileSelectorElement();
|
|
|
|
const profileType = this._findProfileTypeByExtension(file.name);
|
|
if (!profileType) {
|
|
const extensions = new Set(this._profileTypes.map(type => type.fileExtension()).filter(ext => ext));
|
|
Common.console.error(
|
|
Common.UIString(`Can't load file. Supported file extensions: '%s'.`, Array.from(extensions).join(`', '`)));
|
|
return;
|
|
}
|
|
|
|
if (!!profileType.profileBeingRecorded()) {
|
|
Common.console.error(Common.UIString(`Can't load profile while another profile is being recorded.`));
|
|
return;
|
|
}
|
|
|
|
const error = await profileType.loadFromFile(file);
|
|
if (error)
|
|
UI.MessageDialog.show(Common.UIString('Profile loading failed: %s.', error.message));
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
toggleRecord() {
|
|
if (!this._toggleRecordAction.enabled())
|
|
return true;
|
|
const type = this._selectedProfileType;
|
|
const isProfiling = type.buttonClicked();
|
|
this._updateToggleRecordAction(isProfiling);
|
|
if (isProfiling) {
|
|
this._launcherView.profileStarted();
|
|
if (type.hasTemporaryView())
|
|
this.showProfile(type.profileBeingRecorded());
|
|
} else {
|
|
this._launcherView.profileFinished();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
_onSuspendStateChanged() {
|
|
this._updateToggleRecordAction(this._toggleRecordAction.toggled());
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} toggled
|
|
*/
|
|
_updateToggleRecordAction(toggled) {
|
|
const enable = toggled || !SDK.targetManager.allTargetsSuspended();
|
|
this._toggleRecordAction.setEnabled(enable);
|
|
this._toggleRecordAction.setToggled(toggled);
|
|
if (enable)
|
|
this._toggleRecordButton.setTitle(this._selectedProfileType ? this._selectedProfileType.buttonTooltip : '');
|
|
else
|
|
this._toggleRecordButton.setTitle(UI.anotherProfilerActiveLabel());
|
|
if (this._selectedProfileType)
|
|
this._launcherView.updateProfileType(this._selectedProfileType, enable);
|
|
}
|
|
|
|
_profileBeingRecordedRemoved() {
|
|
this._updateToggleRecordAction(false);
|
|
this._launcherView.profileFinished();
|
|
}
|
|
|
|
/**
|
|
* @param {!Common.Event} event
|
|
*/
|
|
_onProfileTypeSelected(event) {
|
|
this._selectedProfileType = /** @type {!Profiler.ProfileType} */ (event.data);
|
|
this._updateProfileTypeSpecificUI();
|
|
}
|
|
|
|
_updateProfileTypeSpecificUI() {
|
|
this._updateToggleRecordAction(this._toggleRecordAction.toggled());
|
|
}
|
|
|
|
_reset() {
|
|
this._profileTypes.forEach(type => type.reset());
|
|
|
|
delete this.visibleView;
|
|
|
|
this._profileGroups = {};
|
|
this._updateToggleRecordAction(false);
|
|
this._launcherView.profileFinished();
|
|
|
|
this._sidebarTree.element.classList.remove('some-expandable');
|
|
|
|
this._launcherView.detach();
|
|
this.profileViews.removeChildren();
|
|
this._profileViewToolbar.removeToolbarItems();
|
|
|
|
this.clearResultsButton.element.classList.remove('hidden');
|
|
this.profilesItemTreeElement.select();
|
|
this._showLauncherView();
|
|
}
|
|
|
|
_showLauncherView() {
|
|
this.closeVisibleView();
|
|
this._profileViewToolbar.removeToolbarItems();
|
|
this._launcherView.show(this.profileViews);
|
|
this.visibleView = this._launcherView;
|
|
this._toolbarElement.classList.add('hidden');
|
|
}
|
|
|
|
/**
|
|
* @param {!Profiler.ProfileType} profileType
|
|
*/
|
|
_registerProfileType(profileType) {
|
|
this._launcherView.addProfileType(profileType);
|
|
const profileTypeSection = new Profiler.ProfileTypeSidebarSection(this, profileType);
|
|
this._typeIdToSidebarSection[profileType.id] = profileTypeSection;
|
|
this._sidebarTree.appendChild(profileTypeSection);
|
|
profileTypeSection.childrenListElement.addEventListener(
|
|
'contextmenu', this._handleContextMenuEvent.bind(this), false);
|
|
|
|
/**
|
|
* @param {!Common.Event} event
|
|
* @this {Profiler.ProfilesPanel}
|
|
*/
|
|
function onAddProfileHeader(event) {
|
|
this._addProfileHeader(/** @type {!Profiler.ProfileHeader} */ (event.data));
|
|
}
|
|
|
|
/**
|
|
* @param {!Common.Event} event
|
|
* @this {Profiler.ProfilesPanel}
|
|
*/
|
|
function onRemoveProfileHeader(event) {
|
|
this._removeProfileHeader(/** @type {!Profiler.ProfileHeader} */ (event.data));
|
|
}
|
|
|
|
/**
|
|
* @param {!Common.Event} event
|
|
* @this {Profiler.ProfilesPanel}
|
|
*/
|
|
function profileComplete(event) {
|
|
this.showProfile(/** @type {!Profiler.ProfileHeader} */ (event.data));
|
|
}
|
|
|
|
profileType.addEventListener(Profiler.ProfileType.Events.ViewUpdated, this._updateProfileTypeSpecificUI, this);
|
|
profileType.addEventListener(Profiler.ProfileType.Events.AddProfileHeader, onAddProfileHeader, this);
|
|
profileType.addEventListener(Profiler.ProfileType.Events.RemoveProfileHeader, onRemoveProfileHeader, this);
|
|
profileType.addEventListener(Profiler.ProfileType.Events.ProfileComplete, profileComplete, this);
|
|
|
|
const profiles = profileType.getProfiles();
|
|
for (let i = 0; i < profiles.length; i++)
|
|
this._addProfileHeader(profiles[i]);
|
|
}
|
|
|
|
/**
|
|
* @param {!Event} event
|
|
*/
|
|
_handleContextMenuEvent(event) {
|
|
const contextMenu = new UI.ContextMenu(event);
|
|
if (this.panelSidebarElement().isSelfOrAncestor(event.srcElement)) {
|
|
contextMenu.defaultSection().appendItem(
|
|
Common.UIString('Load\u2026'), this._fileSelectorElement.click.bind(this._fileSelectorElement));
|
|
}
|
|
contextMenu.show();
|
|
}
|
|
|
|
showLoadFromFileDialog() {
|
|
this._fileSelectorElement.click();
|
|
}
|
|
|
|
/**
|
|
* @param {!Profiler.ProfileHeader} profile
|
|
*/
|
|
_addProfileHeader(profile) {
|
|
const profileType = profile.profileType();
|
|
const typeId = profileType.id;
|
|
this._typeIdToSidebarSection[typeId].addProfileHeader(profile);
|
|
if (!this.visibleView || this.visibleView === this._launcherView)
|
|
this.showProfile(profile);
|
|
}
|
|
|
|
/**
|
|
* @param {!Profiler.ProfileHeader} profile
|
|
*/
|
|
_removeProfileHeader(profile) {
|
|
if (profile.profileType().profileBeingRecorded() === profile)
|
|
this._profileBeingRecordedRemoved();
|
|
|
|
const i = this._indexOfViewForProfile(profile);
|
|
if (i !== -1)
|
|
this._profileToView.splice(i, 1);
|
|
|
|
const profileType = profile.profileType();
|
|
const typeId = profileType.id;
|
|
const sectionIsEmpty = this._typeIdToSidebarSection[typeId].removeProfileHeader(profile);
|
|
|
|
// No other item will be selected if there aren't any other profiles, so
|
|
// make sure that view gets cleared when the last profile is removed.
|
|
if (sectionIsEmpty) {
|
|
this.profilesItemTreeElement.select();
|
|
this._showLauncherView();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {?Profiler.ProfileHeader} profile
|
|
* @return {?UI.Widget}
|
|
*/
|
|
showProfile(profile) {
|
|
if (!profile ||
|
|
(profile.profileType().profileBeingRecorded() === profile) && !profile.profileType().hasTemporaryView())
|
|
return null;
|
|
|
|
const view = this.viewForProfile(profile);
|
|
if (view === this.visibleView)
|
|
return view;
|
|
|
|
this.closeVisibleView();
|
|
|
|
view.show(this.profileViews);
|
|
view.focus();
|
|
this._toolbarElement.classList.remove('hidden');
|
|
this.visibleView = view;
|
|
|
|
const profileTypeSection = this._typeIdToSidebarSection[profile.profileType().id];
|
|
const sidebarElement = profileTypeSection.sidebarElementForProfile(profile);
|
|
sidebarElement.revealAndSelect();
|
|
|
|
this._profileViewToolbar.removeToolbarItems();
|
|
|
|
const toolbarItems = view.syncToolbarItems();
|
|
for (let i = 0; i < toolbarItems.length; ++i)
|
|
this._profileViewToolbar.appendToolbarItem(toolbarItems[i]);
|
|
|
|
return view;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!Protocol.HeapProfiler.HeapSnapshotObjectId} snapshotObjectId
|
|
* @param {string} perspectiveName
|
|
*/
|
|
showObject(snapshotObjectId, perspectiveName) {
|
|
}
|
|
|
|
/**
|
|
* @param {!Profiler.ProfileHeader} profile
|
|
* @return {!UI.Widget}
|
|
*/
|
|
viewForProfile(profile) {
|
|
const index = this._indexOfViewForProfile(profile);
|
|
if (index !== -1)
|
|
return this._profileToView[index].view;
|
|
const view = profile.createView(this);
|
|
view.element.classList.add('profile-view');
|
|
this._profileToView.push({profile: profile, view: view});
|
|
return view;
|
|
}
|
|
|
|
/**
|
|
* @param {!Profiler.ProfileHeader} profile
|
|
* @return {number}
|
|
*/
|
|
_indexOfViewForProfile(profile) {
|
|
for (let i = 0; i < this._profileToView.length; i++) {
|
|
if (this._profileToView[i].profile === profile)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
closeVisibleView() {
|
|
if (this.visibleView)
|
|
this.visibleView.detach();
|
|
delete this.visibleView;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
focus() {
|
|
this._sidebarTree.focus();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @unrestricted
|
|
*/
|
|
Profiler.ProfileTypeSidebarSection = class extends UI.TreeElement {
|
|
/**
|
|
* @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
|
|
* @param {!Profiler.ProfileType} profileType
|
|
*/
|
|
constructor(dataDisplayDelegate, profileType) {
|
|
super(profileType.treeItemTitle.escapeHTML(), true);
|
|
this.selectable = false;
|
|
this._dataDisplayDelegate = dataDisplayDelegate;
|
|
/** @type {!Array<!Profiler.ProfileSidebarTreeElement>} */
|
|
this._profileTreeElements = [];
|
|
/** @type {!Object<string, !Profiler.ProfileTypeSidebarSection.ProfileGroup>} */
|
|
this._profileGroups = {};
|
|
this.expand();
|
|
this.hidden = true;
|
|
this.setCollapsible(false);
|
|
}
|
|
|
|
/**
|
|
* @param {!Profiler.ProfileHeader} profile
|
|
*/
|
|
addProfileHeader(profile) {
|
|
this.hidden = false;
|
|
const profileType = profile.profileType();
|
|
let sidebarParent = this;
|
|
const profileTreeElement = profile.createSidebarTreeElement(this._dataDisplayDelegate);
|
|
this._profileTreeElements.push(profileTreeElement);
|
|
|
|
if (!profile.fromFile() && profileType.profileBeingRecorded() !== profile) {
|
|
const profileTitle = profile.title;
|
|
let group = this._profileGroups[profileTitle];
|
|
if (!group) {
|
|
group = new Profiler.ProfileTypeSidebarSection.ProfileGroup();
|
|
this._profileGroups[profileTitle] = group;
|
|
}
|
|
group.profileSidebarTreeElements.push(profileTreeElement);
|
|
|
|
const groupSize = group.profileSidebarTreeElements.length;
|
|
if (groupSize === 2) {
|
|
// Make a group UI.TreeElement now that there are 2 profiles.
|
|
group.sidebarTreeElement =
|
|
new Profiler.ProfileGroupSidebarTreeElement(this._dataDisplayDelegate, profile.title);
|
|
|
|
const firstProfileTreeElement = group.profileSidebarTreeElements[0];
|
|
// Insert at the same index for the first profile of the group.
|
|
const index = this.children().indexOf(firstProfileTreeElement);
|
|
this.insertChild(group.sidebarTreeElement, index);
|
|
|
|
// Move the first profile to the group.
|
|
const selected = firstProfileTreeElement.selected;
|
|
this.removeChild(firstProfileTreeElement);
|
|
group.sidebarTreeElement.appendChild(firstProfileTreeElement);
|
|
if (selected)
|
|
firstProfileTreeElement.revealAndSelect();
|
|
|
|
firstProfileTreeElement.setSmall(true);
|
|
firstProfileTreeElement.setMainTitle(Common.UIString('Run %d', 1));
|
|
|
|
this.treeOutline.element.classList.add('some-expandable');
|
|
}
|
|
|
|
if (groupSize >= 2) {
|
|
sidebarParent = group.sidebarTreeElement;
|
|
profileTreeElement.setSmall(true);
|
|
profileTreeElement.setMainTitle(Common.UIString('Run %d', groupSize));
|
|
}
|
|
}
|
|
|
|
sidebarParent.appendChild(profileTreeElement);
|
|
}
|
|
|
|
/**
|
|
* @param {!Profiler.ProfileHeader} profile
|
|
* @return {boolean}
|
|
*/
|
|
removeProfileHeader(profile) {
|
|
const index = this._sidebarElementIndex(profile);
|
|
if (index === -1)
|
|
return false;
|
|
const profileTreeElement = this._profileTreeElements[index];
|
|
this._profileTreeElements.splice(index, 1);
|
|
|
|
let sidebarParent = this;
|
|
const group = this._profileGroups[profile.title];
|
|
if (group) {
|
|
const groupElements = group.profileSidebarTreeElements;
|
|
groupElements.splice(groupElements.indexOf(profileTreeElement), 1);
|
|
if (groupElements.length === 1) {
|
|
// Move the last profile out of its group and remove the group.
|
|
const pos = sidebarParent.children().indexOf(
|
|
/** @type {!Profiler.ProfileGroupSidebarTreeElement} */ (group.sidebarTreeElement));
|
|
group.sidebarTreeElement.removeChild(groupElements[0]);
|
|
this.insertChild(groupElements[0], pos);
|
|
groupElements[0].setSmall(false);
|
|
groupElements[0].setMainTitle(profile.title);
|
|
this.removeChild(group.sidebarTreeElement);
|
|
}
|
|
if (groupElements.length !== 0)
|
|
sidebarParent = group.sidebarTreeElement;
|
|
}
|
|
sidebarParent.removeChild(profileTreeElement);
|
|
profileTreeElement.dispose();
|
|
|
|
if (this.childCount())
|
|
return false;
|
|
this.hidden = true;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param {!Profiler.ProfileHeader} profile
|
|
* @return {?Profiler.ProfileSidebarTreeElement}
|
|
*/
|
|
sidebarElementForProfile(profile) {
|
|
const index = this._sidebarElementIndex(profile);
|
|
return index === -1 ? null : this._profileTreeElements[index];
|
|
}
|
|
|
|
/**
|
|
* @param {!Profiler.ProfileHeader} profile
|
|
* @return {number}
|
|
*/
|
|
_sidebarElementIndex(profile) {
|
|
const elements = this._profileTreeElements;
|
|
for (let i = 0; i < elements.length; i++) {
|
|
if (elements[i].profile === profile)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
onattach() {
|
|
this.listItemElement.classList.add('profiles-tree-section');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @unrestricted
|
|
*/
|
|
Profiler.ProfileTypeSidebarSection.ProfileGroup = class {
|
|
constructor() {
|
|
/** @type {!Array<!Profiler.ProfileSidebarTreeElement>} */
|
|
this.profileSidebarTreeElements = [];
|
|
/** @type {?Profiler.ProfileGroupSidebarTreeElement} */
|
|
this.sidebarTreeElement = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @unrestricted
|
|
*/
|
|
Profiler.ProfileSidebarTreeElement = class extends UI.TreeElement {
|
|
/**
|
|
* @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
|
|
* @param {!Profiler.ProfileHeader} profile
|
|
* @param {string} className
|
|
*/
|
|
constructor(dataDisplayDelegate, profile, className) {
|
|
super('', false);
|
|
this._iconElement = createElementWithClass('div', 'icon');
|
|
this._titlesElement = createElementWithClass('div', 'titles no-subtitle');
|
|
this._titleContainer = this._titlesElement.createChild('span', 'title-container');
|
|
this._titleElement = this._titleContainer.createChild('span', 'title');
|
|
this._subtitleElement = this._titlesElement.createChild('span', 'subtitle');
|
|
|
|
this._titleElement.textContent = profile.title;
|
|
this._className = className;
|
|
this._small = false;
|
|
this._dataDisplayDelegate = dataDisplayDelegate;
|
|
this.profile = profile;
|
|
profile.addEventListener(Profiler.ProfileHeader.Events.UpdateStatus, this._updateStatus, this);
|
|
if (profile.canSaveToFile())
|
|
this._createSaveLink();
|
|
else
|
|
profile.addEventListener(Profiler.ProfileHeader.Events.ProfileReceived, this._onProfileReceived, this);
|
|
}
|
|
|
|
_createSaveLink() {
|
|
this._saveLinkElement = this._titleContainer.createChild('span', 'save-link');
|
|
this._saveLinkElement.textContent = Common.UIString('Save');
|
|
this._saveLinkElement.addEventListener('click', this._saveProfile.bind(this), false);
|
|
}
|
|
|
|
_onProfileReceived(event) {
|
|
this._createSaveLink();
|
|
}
|
|
|
|
/**
|
|
* @param {!Common.Event} event
|
|
*/
|
|
_updateStatus(event) {
|
|
const statusUpdate = event.data;
|
|
if (statusUpdate.subtitle !== null) {
|
|
this._subtitleElement.textContent = statusUpdate.subtitle || '';
|
|
this._titlesElement.classList.toggle('no-subtitle', !statusUpdate.subtitle);
|
|
}
|
|
if (typeof statusUpdate.wait === 'boolean' && this.listItemElement)
|
|
this.listItemElement.classList.toggle('wait', statusUpdate.wait);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!Event} event
|
|
* @return {boolean}
|
|
*/
|
|
ondblclick(event) {
|
|
if (!this._editing)
|
|
this._startEditing(/** @type {!Element} */ (event.target));
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param {!Element} eventTarget
|
|
*/
|
|
_startEditing(eventTarget) {
|
|
const container = eventTarget.enclosingNodeOrSelfWithClass('title');
|
|
if (!container)
|
|
return;
|
|
const config = new UI.InplaceEditor.Config(this._editingCommitted.bind(this), this._editingCancelled.bind(this));
|
|
this._editing = UI.InplaceEditor.startEditing(container, config);
|
|
}
|
|
|
|
/**
|
|
* @param {!Element} container
|
|
* @param {string} newTitle
|
|
*/
|
|
_editingCommitted(container, newTitle) {
|
|
delete this._editing;
|
|
this.profile.setTitle(newTitle);
|
|
}
|
|
|
|
_editingCancelled() {
|
|
delete this._editing;
|
|
}
|
|
|
|
dispose() {
|
|
this.profile.removeEventListener(Profiler.ProfileHeader.Events.UpdateStatus, this._updateStatus, this);
|
|
this.profile.removeEventListener(Profiler.ProfileHeader.Events.ProfileReceived, this._onProfileReceived, this);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {boolean}
|
|
*/
|
|
onselect() {
|
|
this._dataDisplayDelegate.showProfile(this.profile);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {boolean}
|
|
*/
|
|
ondelete() {
|
|
this.profile.profileType().removeProfile(this.profile);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
onattach() {
|
|
if (this._className)
|
|
this.listItemElement.classList.add(this._className);
|
|
if (this._small)
|
|
this.listItemElement.classList.add('small');
|
|
this.listItemElement.appendChildren(this._iconElement, this._titlesElement);
|
|
this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true);
|
|
}
|
|
|
|
/**
|
|
* @param {!Event} event
|
|
*/
|
|
_handleContextMenuEvent(event) {
|
|
const profile = this.profile;
|
|
const contextMenu = new UI.ContextMenu(event);
|
|
// FIXME: use context menu provider
|
|
contextMenu.headerSection().appendItem(
|
|
Common.UIString('Load\u2026'),
|
|
Profiler.ProfilesPanel._fileSelectorElement.click.bind(Profiler.ProfilesPanel._fileSelectorElement));
|
|
if (profile.canSaveToFile())
|
|
contextMenu.saveSection().appendItem(Common.UIString('Save\u2026'), profile.saveToFile.bind(profile));
|
|
contextMenu.footerSection().appendItem(Common.UIString('Delete'), this.ondelete.bind(this));
|
|
contextMenu.show();
|
|
}
|
|
|
|
_saveProfile(event) {
|
|
this.profile.saveToFile();
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} small
|
|
*/
|
|
setSmall(small) {
|
|
this._small = small;
|
|
if (this.listItemElement)
|
|
this.listItemElement.classList.toggle('small', this._small);
|
|
}
|
|
|
|
/**
|
|
* @param {string} title
|
|
*/
|
|
setMainTitle(title) {
|
|
this._titleElement.textContent = title;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @unrestricted
|
|
*/
|
|
Profiler.ProfileGroupSidebarTreeElement = class extends UI.TreeElement {
|
|
/**
|
|
* @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
|
|
* @param {string} title
|
|
*/
|
|
constructor(dataDisplayDelegate, title) {
|
|
super('', true);
|
|
this.selectable = false;
|
|
this._dataDisplayDelegate = dataDisplayDelegate;
|
|
this._title = title;
|
|
this.expand();
|
|
this.toggleOnClick = true;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {boolean}
|
|
*/
|
|
onselect() {
|
|
const hasChildren = this.childCount() > 0;
|
|
if (hasChildren)
|
|
this._dataDisplayDelegate.showProfile(this.lastChild().profile);
|
|
return hasChildren;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
onattach() {
|
|
this.listItemElement.classList.add('profile-group-sidebar-tree-item');
|
|
this.listItemElement.createChild('div', 'icon');
|
|
this.listItemElement.createChild('div', 'titles no-subtitle')
|
|
.createChild('span', 'title-container')
|
|
.createChild('span', 'title')
|
|
.textContent = this._title;
|
|
}
|
|
};
|
|
|
|
Profiler.ProfilesSidebarTreeElement = class extends UI.TreeElement {
|
|
/**
|
|
* @param {!Profiler.ProfilesPanel} panel
|
|
*/
|
|
constructor(panel) {
|
|
super('', false);
|
|
this.selectable = true;
|
|
this._panel = panel;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {boolean}
|
|
*/
|
|
onselect() {
|
|
this._panel._showLauncherView();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
onattach() {
|
|
this.listItemElement.classList.add('profile-launcher-view-tree-item');
|
|
this.listItemElement.createChild('div', 'icon');
|
|
this.listItemElement.createChild('div', 'titles no-subtitle')
|
|
.createChild('span', 'title-container')
|
|
.createChild('span', 'title')
|
|
.textContent = Common.UIString('Profiles');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @implements {UI.ActionDelegate}
|
|
*/
|
|
Profiler.JSProfilerPanel = class extends Profiler.ProfilesPanel {
|
|
constructor() {
|
|
const registry = Profiler.ProfileTypeRegistry.instance;
|
|
super('js_profiler', [registry.cpuProfileType], 'profiler.js-toggle-recording');
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
wasShown() {
|
|
UI.context.setFlavor(Profiler.JSProfilerPanel, this);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
willHide() {
|
|
UI.context.setFlavor(Profiler.JSProfilerPanel, null);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!UI.Context} context
|
|
* @param {string} actionId
|
|
* @return {boolean}
|
|
*/
|
|
handleAction(context, actionId) {
|
|
const panel = UI.context.flavor(Profiler.JSProfilerPanel);
|
|
console.assert(panel && panel instanceof Profiler.JSProfilerPanel);
|
|
panel.toggleRecord();
|
|
return true;
|
|
}
|
|
};
|