Commit 6741acb0 authored by alph's avatar alph Committed by Commit bot

DevTools: Introduce Landing page for Timeline panel.

Provide more user-friendly experience as a welcome screen on Timeline panel.
The new UX is put behind an experiment.

BUG=570441

Review-Url: https://codereview.chromium.org/2557973002
Cr-Commit-Position: refs/heads/master@{#437979}
parent 7b5dd288
...@@ -738,6 +738,7 @@ devtools_layer_viewer_js_files = [ ...@@ -738,6 +738,7 @@ devtools_layer_viewer_js_files = [
devtools_timeline_js_files = [ devtools_timeline_js_files = [
"front_end/timeline/invalidationsTree.css", "front_end/timeline/invalidationsTree.css",
"front_end/timeline/timelineFlamechartPopover.css", "front_end/timeline/timelineFlamechartPopover.css",
"front_end/timeline/timelineLandingPage.css",
"front_end/timeline/timelinePaintProfiler.css", "front_end/timeline/timelinePaintProfiler.css",
"front_end/timeline/timelinePanel.css", "front_end/timeline/timelinePanel.css",
"front_end/timeline/timelineStatusDialog.css", "front_end/timeline/timelineStatusDialog.css",
...@@ -748,6 +749,7 @@ devtools_timeline_js_files = [ ...@@ -748,6 +749,7 @@ devtools_timeline_js_files = [
"front_end/timeline/TimelineFlameChart.js", "front_end/timeline/TimelineFlameChart.js",
"front_end/timeline/TimelineFlameChartView.js", "front_end/timeline/TimelineFlameChartView.js",
"front_end/timeline/TimelineNetworkFlameChart.js", "front_end/timeline/TimelineNetworkFlameChart.js",
"front_end/timeline/TimelineLandingPage.js",
"front_end/timeline/TimelineLayersView.js", "front_end/timeline/TimelineLayersView.js",
"front_end/timeline/TimelineLoader.js", "front_end/timeline/TimelineLoader.js",
"front_end/timeline/TimelinePaintProfilerView.js", "front_end/timeline/TimelinePaintProfilerView.js",
......
...@@ -112,6 +112,7 @@ Main.Main = class { ...@@ -112,6 +112,7 @@ Main.Main = class {
Runtime.experiments.register('sourceDiff', 'Source diff'); Runtime.experiments.register('sourceDiff', 'Source diff');
Runtime.experiments.register('terminalInDrawer', 'Terminal in drawer', true); Runtime.experiments.register('terminalInDrawer', 'Terminal in drawer', true);
Runtime.experiments.register('timelineInvalidationTracking', 'Timeline invalidation tracking', true); Runtime.experiments.register('timelineInvalidationTracking', 'Timeline invalidation tracking', true);
Runtime.experiments.register('timelineLandingPage', 'Timeline landing page', true);
Runtime.experiments.register('timelineRecordingPerspectives', 'Timeline recording perspectives UI'); Runtime.experiments.register('timelineRecordingPerspectives', 'Timeline recording perspectives UI');
Runtime.experiments.register('timelineTracingJSProfile', 'Timeline tracing based JS profiler', true); Runtime.experiments.register('timelineTracingJSProfile', 'Timeline tracing based JS profiler', true);
Runtime.experiments.register('timelineV8RuntimeCallStats', 'V8 Runtime Call Stats on Timeline', true); Runtime.experiments.register('timelineV8RuntimeCallStats', 'V8 Runtime Call Stats on Timeline', true);
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
Timeline.TimelineLandingPage = class extends UI.VBox {
constructor() {
super(true);
this.registerRequiredCSS('timeline/timelineLandingPage.css');
this.contentElement.classList.add('timeline-landing-page', 'fill');
const perspectives = Timeline.TimelinePanel.Perspectives;
const config = Timeline.TimelineLandingPage.RecordingConfig;
this._tabbedPane = new UI.TabbedPane();
this._tabbedPane.setTabSlider(true);
this._tabbedPane.renderWithNoHeaderBackground();
var tab = new Timeline.TimelineLandingPage.PerspectiveTabWidget();
tab.setDescription(Common.UIString(
'Page Load mode allows you to analyze how fast the page is loaded and becomes responsive.\n' +
'In this mode the page is automatically reloaded right after the recording has started. ' +
'During recording it collects information about network requests, screen state updates, ' +
'and CPU threads acivity along with JavaScript stacks. ' +
'Recording is stopped automatically shortly after the page processes load event.'));
tab.setAction(recordAndReload);
tab.appendOption(config.network, false, true);
tab.appendOption(config.screenshots, true, true);
this._tabbedPane.appendTab(perspectives.Load, Common.UIString('Page Load'), tab);
tab = new Timeline.TimelineLandingPage.PerspectiveTabWidget();
tab.setDescription(Common.UIString('Record page responsiveness.'));
tab.setAction(record);
tab.appendOption(config.network, false, true);
tab.appendOption(config.screenshots, true, false);
this._tabbedPane.appendTab(perspectives.Responsiveness, Common.UIString('Responsiveness'), tab);
tab = new Timeline.TimelineLandingPage.PerspectiveTabWidget();
tab.setDescription(Common.UIString(
'This mode is useful when you want to focus on JavaScript performance. ' +
'All the options besides sampling CPU profiler are turned off to minimize measurement errors.'));
tab.setAction(record);
this._tabbedPane.appendTab(perspectives.JavaScript, Common.UIString('JavaScript'), tab);
tab = new Timeline.TimelineLandingPage.PerspectiveTabWidget();
tab.setDescription(Common.UIString('Advanced mode that allows you to customize recording options.'));
tab.setAction(record);
tab.appendOption(config.network, true, true);
tab.appendOption(config.javascript, true, true);
tab.appendOption(config.screenshots, true, true);
tab.appendOption(config.memory, true, false);
tab.appendOption(config.paints, true, false);
this._tabbedPane.appendTab(perspectives.Custom, Common.UIString('Custom'), tab);
this._tabbedPane.addEventListener(UI.TabbedPane.Events.TabSelected, this._tabSelected, this);
this._tabbedPane.show(this.contentElement);
this._perspectiveSetting =
Common.settings.createSetting('timelinePerspective', Timeline.TimelinePanel.Perspectives.Load);
this._perspectiveSetting.addChangeListener(this._perspectiveChanged, this);
function record() {
UI.actionRegistry.action('timeline.toggle-recording').execute();
}
function recordAndReload() {
SDK.targetManager.reloadPage();
}
}
/**
* @param {!Common.Event} event
*/
_tabSelected(event) {
if (this._perspectiveSetting.get() !== event.data.tabId)
this._perspectiveSetting.set(event.data.tabId);
}
_perspectiveChanged() {
this._tabbedPane.selectTab(this._perspectiveSetting.get());
const tabWidget = /** @type {!Timeline.TimelineLandingPage.PerspectiveTabWidget} */ (this._tabbedPane.visibleView);
tabWidget.activate();
}
};
/** @typedef {!{id: string, title: string, description: string, setting: string}} */
Timeline.TimelineLandingPage.RecordingOption;
/** @type {!Object<string, !Timeline.TimelineLandingPage.RecordingOption>} */
Timeline.TimelineLandingPage.RecordingConfig = {
network: {
id: 'network',
title: Common.UIString('Network'),
description: Common.UIString('Capture network requests information.'),
setting: 'timelineCaptureNetwork'
},
javascript: {
id: 'javascript',
title: Common.UIString('JavaScript'),
description: Common.UIString('Use sampling CPU profiler to collect JavaScript stacks.'),
setting: 'timelineEnableJSSampling'
},
screenshots: {
id: 'screenshots',
title: Common.UIString('Screenshots'),
description:
Common.UIString('Collect page screenshots, so you can observe how the page was evolving during recording.'),
setting: 'timelineCaptureFilmStrip'
},
paints: {
id: 'paints',
title: Common.UIString('Paints'),
description: Common.UIString(
'Capture graphics layer positions and rasterization draw calls (moderate performance overhead).'),
setting: 'timelineCaptureLayersAndPictures'
},
memory: {
id: 'memory',
title: Common.UIString('Memory'),
description: Common.UIString('Capture memory statistics on every timeline event.'),
setting: 'timelineCaptureMemory'
}
};
Timeline.TimelineLandingPage.PerspectiveTabWidget = class extends UI.VBox {
constructor() {
super(false);
this.contentElement.classList.add('timeline-perspective-body');
this._enabledOptions = new Set([Timeline.TimelineLandingPage.RecordingConfig.javascript.id]);
this._descriptionDiv = this.contentElement.createChild('div', 'timeline-perspective-description');
this._actionButton = createTextButton(Common.UIString('Start'));
this._actionButtonDiv = this.contentElement.createChild('div');
this._actionButtonDiv.appendChild(this._actionButton);
}
/**
* @param {string} text
*/
setDescription(text) {
this._descriptionDiv.textContent = text;
}
/**
* @param {function()} action
*/
setAction(action) {
this._actionButton.addEventListener('click', action);
}
/**
* @param {!Timeline.TimelineLandingPage.RecordingOption} option
* @param {boolean} visible
* @param {boolean} enabled
*/
appendOption(option, visible, enabled) {
if (enabled)
this._enabledOptions.add(option.id);
if (!visible)
return;
const div = createElementWithClass('div', 'recording-setting');
const value = this._enabledOptions.has(option.id);
const setting = Common.settings.createSetting(option.setting, value);
div.appendChild(UI.SettingsUI.createSettingCheckbox(option.title, setting, true));
if (option.description)
div.createChild('div', 'recording-setting-description').textContent = option.description;
this.contentElement.insertBefore(div, this._actionButtonDiv);
}
activate() {
for (const id in Timeline.TimelineLandingPage.RecordingConfig) {
const config = Timeline.TimelineLandingPage.RecordingConfig[id];
const setting = Common.settings.createSetting(config.setting, false);
setting.set(this._enabledOptions.has(id));
}
}
};
...@@ -83,16 +83,16 @@ Timeline.TimelinePanel = class extends UI.Panel { ...@@ -83,16 +83,16 @@ Timeline.TimelinePanel = class extends UI.Panel {
this._panelToolbar = new UI.Toolbar('', this.element); this._panelToolbar = new UI.Toolbar('', this.element);
var timelinePane = new UI.VBox(); this._timelinePane = new UI.VBox();
timelinePane.show(this.element); this._timelinePane.show(this.element);
var topPaneElement = timelinePane.element.createChild('div', 'hbox'); var topPaneElement = this._timelinePane.element.createChild('div', 'hbox');
topPaneElement.id = 'timeline-overview-panel'; topPaneElement.id = 'timeline-overview-panel';
// Create top overview component. // Create top overview component.
this._overviewPane = new UI.TimelineOverviewPane('timeline'); this._overviewPane = new UI.TimelineOverviewPane('timeline');
this._overviewPane.addEventListener(UI.TimelineOverviewPane.Events.WindowChanged, this._onWindowChanged.bind(this)); this._overviewPane.addEventListener(UI.TimelineOverviewPane.Events.WindowChanged, this._onWindowChanged.bind(this));
this._overviewPane.show(topPaneElement); this._overviewPane.show(topPaneElement);
this._statusPaneContainer = timelinePane.element.createChild('div', 'status-pane-container fill'); this._statusPaneContainer = this._timelinePane.element.createChild('div', 'status-pane-container fill');
this._createFileSelector(); this._createFileSelector();
...@@ -122,7 +122,7 @@ Timeline.TimelinePanel = class extends UI.Panel { ...@@ -122,7 +122,7 @@ Timeline.TimelinePanel = class extends UI.Panel {
this._captureMemorySetting.addChangeListener(this._onModeChanged, this); this._captureMemorySetting.addChangeListener(this._onModeChanged, this);
this._captureFilmStripSetting.addChangeListener(this._onModeChanged, this); this._captureFilmStripSetting.addChangeListener(this._onModeChanged, this);
this._detailsSplitWidget.show(timelinePane.element); this._detailsSplitWidget.show(this._timelinePane.element);
this._detailsSplitWidget.hideSidebar(); this._detailsSplitWidget.hideSidebar();
SDK.targetManager.addEventListener(SDK.TargetManager.Events.SuspendStateChanged, this._onSuspendStateChanged, this); SDK.targetManager.addEventListener(SDK.TargetManager.Events.SuspendStateChanged, this._onSuspendStateChanged, this);
this._showRecordingHelpMessage(); this._showRecordingHelpMessage();
...@@ -410,7 +410,7 @@ Timeline.TimelinePanel = class extends UI.Panel { ...@@ -410,7 +410,7 @@ Timeline.TimelinePanel = class extends UI.Panel {
* @param {string} name * @param {string} name
* @param {number} value * @param {number} value
*/ */
function addGroupingOption(name, value) { function addOption(name, value) {
var option = cpuThrottlingCombobox.createOption(name, '', String(value)); var option = cpuThrottlingCombobox.createOption(name, '', String(value));
cpuThrottlingCombobox.addOption(option); cpuThrottlingCombobox.addOption(option);
if (hasSelection || (value && value !== currentRate)) if (hasSelection || (value && value !== currentRate))
...@@ -418,13 +418,9 @@ Timeline.TimelinePanel = class extends UI.Panel { ...@@ -418,13 +418,9 @@ Timeline.TimelinePanel = class extends UI.Panel {
cpuThrottlingCombobox.select(option); cpuThrottlingCombobox.select(option);
hasSelection = true; hasSelection = true;
} }
var predefinedRates = new Map([ addOption(Common.UIString('No CPU throttling'), 1);
[1, Common.UIString('No CPU throttling')], [2, Common.UIString('2\xD7 slowdown')], for (const rate of [2, 5, 10, 20])
[5, Common.UIString('5\xD7 slowdown')], [10, Common.UIString('10\xD7 slowdown')], addOption(Common.UIString('%d\xD7 slowdown', rate), rate);
[20, Common.UIString('20\xD7 slowdown')]
]);
for (var rate of predefinedRates)
addGroupingOption(rate[1], rate[0]);
} }
_prepareToLoadTimeline() { _prepareToLoadTimeline() {
...@@ -633,32 +629,34 @@ Timeline.TimelinePanel = class extends UI.Panel { ...@@ -633,32 +629,34 @@ Timeline.TimelinePanel = class extends UI.Panel {
} }
_clear() { _clear() {
this._showRecordingHelpMessage();
this._detailsSplitWidget.hideSidebar();
this._reset();
}
_reset() {
if (Runtime.experiments.isEnabled('timelineRuleUsageRecording') && this._markUnusedCSS.get()) if (Runtime.experiments.isEnabled('timelineRuleUsageRecording') && this._markUnusedCSS.get())
Components.CoverageProfile.instance().reset(); Components.CoverageProfile.instance().reset();
Components.LineLevelProfile.instance().reset(); Components.LineLevelProfile.instance().reset();
this._tracingModel.reset(); this._tracingModel.reset();
this._model.reset(); this._model.reset();
this._showRecordingHelpMessage();
this.requestWindowTimes(0, Infinity); this.requestWindowTimes(0, Infinity);
delete this._selection; delete this._selection;
this._frameModel.reset(); this._frameModel.reset();
this._filmStripModel.reset(this._tracingModel); this._filmStripModel.reset(this._tracingModel);
this._overviewPane.reset(); this._overviewPane.reset();
for (var i = 0; i < this._currentViews.length; ++i) this._currentViews.forEach(view => view.reset());
this._currentViews[i].reset(); this._overviewControls.forEach(overview => overview.reset());
for (var i = 0; i < this._overviewControls.length; ++i)
this._overviewControls[i].reset();
this.select(null); this.select(null);
this._detailsSplitWidget.hideSidebar();
} }
/** /**
* @override * @override
*/ */
recordingStarted() { recordingStarted() {
this._clear(); this._reset();
this._setState(Timeline.TimelinePanel.State.Recording); this._setState(Timeline.TimelinePanel.State.Recording);
this._showRecordingStarted(); this._showRecordingStarted();
this._statusPane.updateStatus(Common.UIString('Recording\u2026')); this._statusPane.updateStatus(Common.UIString('Recording\u2026'));
...@@ -676,6 +674,11 @@ Timeline.TimelinePanel = class extends UI.Panel { ...@@ -676,6 +674,11 @@ Timeline.TimelinePanel = class extends UI.Panel {
} }
_showRecordingHelpMessage() { _showRecordingHelpMessage() {
if (Runtime.experiments.isEnabled('timelineLandingPage')) {
this._showLandingPage();
return;
}
/** /**
* @param {string} tagName * @param {string} tagName
* @param {string} contents * @param {string} contents
...@@ -710,11 +713,31 @@ Timeline.TimelinePanel = class extends UI.Panel { ...@@ -710,11 +713,31 @@ Timeline.TimelinePanel = class extends UI.Panel {
} }
_hideRecordingHelpMessage() { _hideRecordingHelpMessage() {
if (Runtime.experiments.isEnabled('timelineLandingPage')) {
this._hideLandingPage();
return;
}
if (this._helpMessageElement) if (this._helpMessageElement)
this._helpMessageElement.remove(); this._helpMessageElement.remove();
delete this._helpMessageElement; delete this._helpMessageElement;
} }
_showLandingPage() {
if (this._landingPage)
return;
this._detailsSplitWidget.detach();
this._landingPage = new Timeline.TimelineLandingPage();
this._landingPage.show(this._timelinePane.element);
}
_hideLandingPage() {
if (!this._landingPage)
return;
this._landingPage.detach();
this._landingPage = null;
this._detailsSplitWidget.show(this._timelinePane.element);
}
/** /**
* @override * @override
*/ */
......
...@@ -131,6 +131,7 @@ ...@@ -131,6 +131,7 @@
"TimelineFlameChart.js", "TimelineFlameChart.js",
"TimelineNetworkFlameChart.js", "TimelineNetworkFlameChart.js",
"TimelineTreeView.js", "TimelineTreeView.js",
"TimelineLandingPage.js",
"TimelineUIUtils.js", "TimelineUIUtils.js",
"TimelineLayersView.js", "TimelineLayersView.js",
"TimelinePaintProfilerView.js", "TimelinePaintProfilerView.js",
...@@ -139,6 +140,7 @@ ...@@ -139,6 +140,7 @@
"resources": [ "resources": [
"invalidationsTree.css", "invalidationsTree.css",
"timelineFlamechartPopover.css", "timelineFlamechartPopover.css",
"timelineLandingPage.css",
"timelinePanel.css", "timelinePanel.css",
"timelinePaintProfiler.css", "timelinePaintProfiler.css",
"timelineStatusDialog.css" "timelineStatusDialog.css"
......
/*
* Copyright (c) 2016 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
.timeline-landing-page {
align-self: center;
max-width: 500px;
font-size: 13px;
}
.timeline-landing-page .tabbed-pane {
padding: 10px 0 0 0;
}
.timeline-landing-page .timeline-perspective-body {
align-items: stretch;
line-height: 1.5;
padding: 12px;
white-space: pre-line;
width: 100%;
font-size: 12px;
}
.timeline-landing-page .timeline-perspective-body > div {
margin-bottom: 12px;
flex-shrink: 0;
}
.timeline-landing-page button {
min-width: 90px;
min-height: 26px;
margin: 10px 0 0 0;
}
.timeline-landing-page .recording-setting {
display: flex;
align-items: flex-start;
margin-bottom: 2px !important;
}
.timeline-landing-page .recording-setting label {
flex: 0 0 100px;
}
.timeline-landing-page .recording-setting-description {
color: #999;
}
...@@ -455,6 +455,7 @@ UI.TabbedPane = class extends UI.VBox { ...@@ -455,6 +455,7 @@ UI.TabbedPane = class extends UI.VBox {
setTabSlider(enable) { setTabSlider(enable) {
this._sliderEnabled = enable; this._sliderEnabled = enable;
this._tabSlider.classList.toggle('enabled', enable); this._tabSlider.classList.toggle('enabled', enable);
this._headerElement.classList.add('tabbed-pane-no-tab-borders');
} }
/** /**
......
...@@ -269,6 +269,15 @@ ...@@ -269,6 +269,15 @@
background-color: transparent; background-color: transparent;
} }
.tabbed-pane-no-tab-borders .tabbed-pane-header-tab {
border: none;
padding: 2px 10px;
}
.tabbed-pane-no-tab-borders .tabbed-pane-header-contents {
margin-left: 0;
}
.tabbed-pane-left-toolbar { .tabbed-pane-left-toolbar {
margin-right: -4px; margin-right: -4px;
flex: none; flex: none;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment