2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 * Copyright (C) 2012 Intel Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 * @extends {WebInspector.Panel}
35 * @implements {WebInspector.TimelineModeViewDelegate}
36 * @implements {WebInspector.Searchable}
38 WebInspector.TimelinePanel = function()
40 WebInspector.Panel.call(this, "timeline");
41 this.registerRequiredCSS("timelinePanel.css");
42 this.registerRequiredCSS("layersPanel.css");
43 this.registerRequiredCSS("filter.css");
44 this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false);
46 this._detailsLinkifier = new WebInspector.Linkifier();
47 this._windowStartTime = 0;
48 this._windowEndTime = Infinity;
51 if (Runtime.experiments.isEnabled("timelineOnTraceEvents")) {
52 this._tracingManager = new WebInspector.TracingManager();
53 this._tracingManager.addEventListener(WebInspector.TracingManager.Events.BufferUsage, this._onTracingBufferUsage, this);
55 this._tracingModel = new WebInspector.TracingModel();
56 this._uiUtils = new WebInspector.TracingTimelineUIUtils();
57 this._tracingTimelineModel = new WebInspector.TracingTimelineModel(this._tracingManager, this._tracingModel, this._uiUtils.hiddenRecordsFilter());
58 this._model = this._tracingTimelineModel;
60 this._uiUtils = new WebInspector.TimelineUIUtilsImpl();
61 this._model = new WebInspector.TimelineModelImpl();
64 this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
65 this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStopped, this._onRecordingStopped, this);
66 this._model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this);
67 this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingProgress, this._onRecordingProgress, this);
68 this._model.addEventListener(WebInspector.TimelineModel.Events.RecordFilterChanged, this._refreshViews, this);
69 this._model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this);
71 this._categoryFilter = new WebInspector.TimelineCategoryFilter(this._uiUtils);
72 this._durationFilter = new WebInspector.TimelineIsLongFilter();
73 this._textFilter = new WebInspector.TimelineTextFilter(this._uiUtils);
75 var hiddenEmptyRecordsFilter = this._uiUtils.hiddenEmptyRecordsFilter();
76 if (hiddenEmptyRecordsFilter)
77 this._model.addFilter(hiddenEmptyRecordsFilter);
78 this._model.addFilter(this._uiUtils.hiddenRecordsFilter());
79 this._model.addFilter(this._categoryFilter);
80 this._model.addFilter(this._durationFilter);
81 this._model.addFilter(this._textFilter);
83 /** @type {!Array.<!WebInspector.TimelineModeView>} */
84 this._currentViews = [];
86 this._overviewModeSetting = WebInspector.settings.createSetting("timelineOverviewMode", WebInspector.TimelinePanel.OverviewMode.Events);
87 this._flameChartEnabledSetting = WebInspector.settings.createSetting("timelineFlameChartEnabled", false);
88 this._createStatusBarItems();
90 var topPaneElement = this.element.createChild("div", "hbox");
91 topPaneElement.id = "timeline-overview-panel";
93 // Create top overview component.
94 this._overviewPane = new WebInspector.TimelineOverviewPane(this._model, this._uiUtils);
95 this._overviewPane.addEventListener(WebInspector.TimelineOverviewPane.Events.WindowChanged, this._onWindowChanged.bind(this));
96 this._overviewPane.show(topPaneElement);
98 this._createFileSelector();
99 this._registerShortcuts();
101 WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.WillReloadPage, this._willReloadPage, this);
102 WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.Load, this._loadEventFired, this);
104 // Create top level properties splitter.
105 this._detailsSplitView = new WebInspector.SplitView(false, true, "timelinePanelDetailsSplitViewState");
106 this._detailsSplitView.element.classList.add("timeline-details-split");
107 this._detailsSplitView.sidebarElement().classList.add("timeline-details");
108 this._detailsView = new WebInspector.TimelineDetailsView();
109 this._detailsSplitView.installResizer(this._detailsView.headerElement());
110 this._detailsView.show(this._detailsSplitView.sidebarElement());
112 this._searchableView = new WebInspector.SearchableView(this);
113 this._searchableView.setMinimumSize(0, 25);
114 this._searchableView.element.classList.add("searchable-view");
115 this._searchableView.show(this._detailsSplitView.mainElement());
117 this._stackView = new WebInspector.StackView(false);
118 this._stackView.show(this._searchableView.element);
119 this._stackView.element.classList.add("timeline-view-stack");
121 WebInspector.dockController.addEventListener(WebInspector.DockController.Events.DockSideChanged, this._dockSideChanged.bind(this));
122 WebInspector.settings.splitVerticallyWhenDockedToRight.addChangeListener(this._dockSideChanged.bind(this));
123 this._dockSideChanged();
125 this._onModeChanged();
126 this._detailsSplitView.show(this.element);
127 WebInspector.profilingLock().addEventListener(WebInspector.Lock.Events.StateChanged, this._onProfilingStateChanged, this);
130 WebInspector.TimelinePanel.OverviewMode = {
135 // Define row and header height, should be in sync with styles for timeline graphs.
136 WebInspector.TimelinePanel.rowHeight = 18;
137 WebInspector.TimelinePanel.headerHeight = 20;
139 WebInspector.TimelinePanel.durationFilterPresetsMs = [0, 1, 15];
141 WebInspector.TimelinePanel.prototype = {
143 * @return {?WebInspector.SearchableView}
145 searchableView: function()
147 return this._searchableView;
152 if (!WebInspector.TimelinePanel._categoryStylesInitialized) {
153 WebInspector.TimelinePanel._categoryStylesInitialized = true;
154 var style = document.createElement("style");
155 var categories = WebInspector.TimelineUIUtils.categories();
156 style.textContent = Object.values(categories).map(WebInspector.TimelineUIUtils.createStyleRuleForCategory).join("\n");
157 document.head.appendChild(style);
161 _dockSideChanged: function()
163 var dockSide = WebInspector.dockController.dockSide();
164 var vertically = false;
165 if (dockSide === WebInspector.DockController.State.DockedToBottom)
168 vertically = !WebInspector.settings.splitVerticallyWhenDockedToRight.get();
169 this._detailsSplitView.setVertical(vertically);
170 this._detailsView.setVertical(vertically);
176 windowStartTime: function()
178 if (this._windowStartTime)
179 return this._windowStartTime;
180 return this._model.minimumRecordTime();
186 windowEndTime: function()
188 if (this._windowEndTime < Infinity)
189 return this._windowEndTime;
190 return this._model.maximumRecordTime() || Infinity;
194 * @param {!WebInspector.Event} event
196 _sidebarResized: function(event)
198 var width = /** @type {number} */ (event.data);
199 for (var i = 0; i < this._currentViews.length; ++i)
200 this._currentViews[i].setSidebarSize(width);
204 * @param {!WebInspector.Event} event
206 _onWindowChanged: function(event)
208 this._windowStartTime = event.data.startTime;
209 this._windowEndTime = event.data.endTime;
211 for (var i = 0; i < this._currentViews.length; ++i)
212 this._currentViews[i].setWindowTimes(this._windowStartTime, this._windowEndTime);
216 * @param {number} windowStartTime
217 * @param {number} windowEndTime
219 requestWindowTimes: function(windowStartTime, windowEndTime)
221 this._overviewPane.requestWindowTimes(windowStartTime, windowEndTime);
225 * @return {!WebInspector.TimelineFrameModelBase}
227 _frameModel: function()
229 if (this._lazyFrameModel)
230 return this._lazyFrameModel;
231 if (this._tracingModel) {
232 var tracingFrameModel = new WebInspector.TracingTimelineFrameModel();
233 tracingFrameModel.addTraceEvents(this._tracingTimelineModel.inspectedTargetEvents(), this._tracingModel.sessionId() || "");
234 this._lazyFrameModel = tracingFrameModel;
236 var frameModel = new WebInspector.TimelineFrameModel();
237 frameModel.addRecords(this._model.records());
238 this._lazyFrameModel = frameModel;
240 return this._lazyFrameModel;
244 * @return {!WebInspector.TimelineView}
246 _timelineView: function()
248 if (!this._lazyTimelineView)
249 this._lazyTimelineView = new WebInspector.TimelineView(this, this._model, this._uiUtils);
250 return this._lazyTimelineView;
254 * @return {!WebInspector.View}
256 _layersView: function()
258 if (this._lazyLayersView)
259 return this._lazyLayersView;
260 this._lazyLayersView = new WebInspector.TimelineLayersView();
261 this._lazyLayersView.setTimelineModelAndDelegate(this._model, this);
262 return this._lazyLayersView;
265 _paintProfilerView: function()
267 if (this._lazyPaintProfilerView)
268 return this._lazyPaintProfilerView;
269 this._lazyPaintProfilerView = new WebInspector.TimelinePaintProfilerView();
270 return this._lazyPaintProfilerView;
274 * @param {!WebInspector.TimelineModeView} modeView
276 _addModeView: function(modeView)
278 modeView.setWindowTimes(this.windowStartTime(), this.windowEndTime());
279 modeView.refreshRecords(this._textFilter._regex);
280 this._stackView.appendView(modeView.view(), "timelinePanelTimelineStackSplitViewState");
281 modeView.view().addEventListener(WebInspector.SplitView.Events.SidebarSizeChanged, this._sidebarResized, this);
282 this._currentViews.push(modeView);
285 _removeAllModeViews: function()
287 for (var i = 0; i < this._currentViews.length; ++i) {
288 this._currentViews[i].removeEventListener(WebInspector.SplitView.Events.SidebarSizeChanged, this._sidebarResized, this);
289 this._currentViews[i].dispose();
291 this._currentViews = [];
292 this._stackView.detachChildViews();
296 * @param {string} name
297 * @param {!WebInspector.Setting} setting
298 * @param {string} tooltip
301 _createSettingCheckbox: function(name, setting, tooltip)
303 if (!this._recordingOptionUIControls)
304 this._recordingOptionUIControls = [];
306 var checkboxElement = document.createElement("input");
307 var labelElement = WebInspector.SettingsUI.createSettingCheckbox(name, setting, true, checkboxElement, tooltip);
308 this._recordingOptionUIControls.push({ "label": labelElement, "checkbox": checkboxElement });
312 _createStatusBarItems: function()
314 var panelStatusBarElement = this.element.createChild("div", "panel-status-bar");
315 this._statusBarButtons = /** @type {!Array.<!WebInspector.StatusBarItem>} */ ([]);
317 this.toggleTimelineButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item");
318 this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked, this);
319 this._statusBarButtons.push(this.toggleTimelineButton);
320 panelStatusBarElement.appendChild(this.toggleTimelineButton.element);
321 this._updateToggleTimelineButton(false);
323 var clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
324 clearButton.addEventListener("click", this._onClearButtonClick, this);
325 this._statusBarButtons.push(clearButton);
326 panelStatusBarElement.appendChild(clearButton.element);
328 this._filterBar = this._createFilterBar();
329 panelStatusBarElement.appendChild(this._filterBar.filterButton().element);
331 var garbageCollectButton = new WebInspector.StatusBarButton(WebInspector.UIString("Collect Garbage"), "timeline-garbage-collect-status-bar-item");
332 garbageCollectButton.addEventListener("click", this._garbageCollectButtonClicked, this);
333 this._statusBarButtons.push(garbageCollectButton);
334 panelStatusBarElement.appendChild(garbageCollectButton.element);
336 var framesToggleButton = new WebInspector.StatusBarButton(WebInspector.UIString("Frames mode"), "timeline-frames-status-bar-item");
337 framesToggleButton.toggled = this._overviewModeSetting.get() === WebInspector.TimelinePanel.OverviewMode.Frames;
338 framesToggleButton.addEventListener("click", this._overviewModeChanged.bind(this, framesToggleButton));
339 this._statusBarButtons.push(framesToggleButton);
340 panelStatusBarElement.appendChild(framesToggleButton.element);
342 if (Runtime.experiments.isEnabled("timelineOnTraceEvents")) {
343 var flameChartToggleButton = new WebInspector.StatusBarButton(WebInspector.UIString("Tracing mode"), "timeline-flame-chart-status-bar-item");
344 flameChartToggleButton.toggled = this._flameChartEnabledSetting.get();
345 flameChartToggleButton.addEventListener("click", this._flameChartEnabledChanged.bind(this, flameChartToggleButton));
346 this._statusBarButtons.push(flameChartToggleButton);
347 panelStatusBarElement.appendChild(flameChartToggleButton.element);
350 this._captureStacksSetting = WebInspector.settings.createSetting("timelineCaptureStacks", true);
351 this._captureStacksSetting.addChangeListener(this._refreshViews, this);
352 panelStatusBarElement.appendChild(this._createSettingCheckbox(WebInspector.UIString("Stacks"),
353 this._captureStacksSetting,
354 WebInspector.UIString("Capture JavaScript stack on every timeline event")));
355 this._captureMemorySetting = WebInspector.settings.createSetting("timelineCaptureMemory", false);
356 panelStatusBarElement.appendChild(this._createSettingCheckbox(WebInspector.UIString("Memory"),
357 this._captureMemorySetting,
358 WebInspector.UIString("Capture memory information on every timeline event")));
359 this._captureMemorySetting.addChangeListener(this._onModeChanged, this);
360 if (Runtime.experiments.isEnabled("timelinePowerProfiler") &&
361 WebInspector.targetManager.mainTarget().hasCapability(WebInspector.Target.Capabilities.CanProfilePower)) {
362 this._capturePowerSetting = WebInspector.settings.createSetting("timelineCapturePower", false);
363 panelStatusBarElement.appendChild(this._createSettingCheckbox(WebInspector.UIString("Power"),
364 this._capturePowerSetting,
365 WebInspector.UIString("Capture power information")));
366 this._capturePowerSetting.addChangeListener(this._onModeChanged, this);
368 if (Runtime.experiments.isEnabled("timelineOnTraceEvents")) {
369 this._captureLayersAndPicturesSetting = WebInspector.settings.createSetting("timelineCaptureLayersAndPictures", false);
370 panelStatusBarElement.appendChild(this._createSettingCheckbox(WebInspector.UIString("Paint"),
371 this._captureLayersAndPicturesSetting,
372 WebInspector.UIString("Capture graphics layer positions and painted pictures")));
375 this._miscStatusBarItems = panelStatusBarElement.createChild("div", "status-bar-item");
377 this._filtersContainer = this.element.createChild("div", "timeline-filters-header hidden");
378 this._filtersContainer.appendChild(this._filterBar.filtersElement());
379 this._filterBar.addEventListener(WebInspector.FilterBar.Events.FiltersToggled, this._onFiltersToggled, this);
380 this._filterBar.setName("timelinePanel");
381 if (!Runtime.experiments.isEnabled("timelineOnTraceEvents")) {
382 var targetsComboBox = new WebInspector.StatusBarComboBox(null);
383 panelStatusBarElement.appendChild(targetsComboBox.element);
384 new WebInspector.TargetsComboBoxController(targetsComboBox.selectElement(), targetsComboBox.element);
389 * @return {!WebInspector.FilterBar}
391 _createFilterBar: function()
393 this._filterBar = new WebInspector.FilterBar();
395 this._filters._textFilterUI = new WebInspector.TextFilterUI();
396 this._filters._textFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._textFilterChanged, this);
397 this._filterBar.addFilter(this._filters._textFilterUI);
399 var durationOptions = [];
400 for (var presetIndex = 0; presetIndex < WebInspector.TimelinePanel.durationFilterPresetsMs.length; ++presetIndex) {
401 var durationMs = WebInspector.TimelinePanel.durationFilterPresetsMs[presetIndex];
402 var durationOption = {};
404 durationOption.label = WebInspector.UIString("All");
405 durationOption.title = WebInspector.UIString("Show all records");
407 durationOption.label = WebInspector.UIString("\u2265 %dms", durationMs);
408 durationOption.title = WebInspector.UIString("Hide records shorter than %dms", durationMs);
410 durationOption.value = durationMs;
411 durationOptions.push(durationOption);
413 this._filters._durationFilterUI = new WebInspector.ComboBoxFilterUI(durationOptions);
414 this._filters._durationFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._durationFilterChanged, this);
415 this._filterBar.addFilter(this._filters._durationFilterUI);
417 this._filters._categoryFiltersUI = {};
418 var categoryTypes = [];
419 var categories = WebInspector.TimelineUIUtils.categories();
420 for (var categoryName in categories) {
421 var category = categories[categoryName];
422 if (category.overviewStripGroupIndex < 0)
424 var filter = new WebInspector.CheckboxFilterUI(category.name, category.title);
425 this._filters._categoryFiltersUI[category.name] = filter;
426 filter.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._categoriesFilterChanged.bind(this, categoryName), this);
427 this._filterBar.addFilter(filter);
429 return this._filterBar;
432 _textFilterChanged: function(event)
434 var searchQuery = this._filters._textFilterUI.value();
435 this.searchCanceled();
436 this._textFilter.setRegex(searchQuery ? createPlainTextSearchRegex(searchQuery, "i") : null);
439 _durationFilterChanged: function()
441 var duration = this._filters._durationFilterUI.value();
442 var minimumRecordDuration = parseInt(duration, 10);
443 this._durationFilter.setMinimumRecordDuration(minimumRecordDuration);
446 _categoriesFilterChanged: function(name, event)
448 var categories = WebInspector.TimelineUIUtils.categories();
449 categories[name].hidden = !this._filters._categoryFiltersUI[name].checked();
450 this._categoryFilter.notifyFilterChanged();
453 _onFiltersToggled: function(event)
455 var toggled = /** @type {boolean} */ (event.data);
456 this._filtersContainer.classList.toggle("hidden", !toggled);
461 * @return {?WebInspector.ProgressIndicator}
463 _prepareToLoadTimeline: function()
465 if (this._operationInProgress)
467 if (this._recordingInProgress()) {
468 this._updateToggleTimelineButton(false);
469 this._stopRecording();
471 var progressIndicator = new WebInspector.ProgressIndicator();
472 progressIndicator.addEventListener(WebInspector.Progress.Events.Done, this._setOperationInProgress.bind(this, null));
473 this._setOperationInProgress(progressIndicator);
474 return progressIndicator;
478 * @param {?WebInspector.ProgressIndicator} indicator
480 _setOperationInProgress: function(indicator)
482 this._operationInProgress = !!indicator;
483 for (var i = 0; i < this._statusBarButtons.length; ++i)
484 this._statusBarButtons[i].setEnabled(!this._operationInProgress);
485 this._miscStatusBarItems.removeChildren();
487 this._miscStatusBarItems.appendChild(indicator.element);
490 _registerShortcuts: function()
492 this.registerShortcuts(WebInspector.ShortcutsScreen.TimelinePanelShortcuts.StartStopRecording, this._toggleTimelineButtonClicked.bind(this));
493 this.registerShortcuts(WebInspector.ShortcutsScreen.TimelinePanelShortcuts.SaveToFile, this._saveToFile.bind(this));
494 this.registerShortcuts(WebInspector.ShortcutsScreen.TimelinePanelShortcuts.LoadFromFile, this._selectFileToLoad.bind(this));
497 _createFileSelector: function()
499 if (this._fileSelectorElement)
500 this._fileSelectorElement.remove();
502 this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this));
503 this.element.appendChild(this._fileSelectorElement);
506 _contextMenu: function(event)
508 var contextMenu = new WebInspector.ContextMenu(event);
509 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save Timeline data\u2026" : "Save Timeline Data\u2026"), this._saveToFile.bind(this), this._operationInProgress);
510 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Load Timeline data\u2026" : "Load Timeline Data\u2026"), this._selectFileToLoad.bind(this), this._operationInProgress);
517 _saveToFile: function()
519 if (this._operationInProgress)
521 this._model.saveToFile();
528 _selectFileToLoad: function() {
529 this._fileSelectorElement.click();
534 * @param {!File} file
536 _loadFromFile: function(file)
538 var progressIndicator = this._prepareToLoadTimeline();
539 if (!progressIndicator)
541 this._model.loadFromFile(file, progressIndicator);
542 this._createFileSelector();
545 _refreshViews: function()
547 for (var i = 0; i < this._currentViews.length; ++i) {
548 var view = this._currentViews[i];
549 view.refreshRecords(this._textFilter._regex);
551 this._updateSelectedRangeStats();
555 * @param {!WebInspector.StatusBarButton} button
557 _overviewModeChanged: function(button)
559 var oldMode = this._overviewModeSetting.get();
560 if (oldMode === WebInspector.TimelinePanel.OverviewMode.Events) {
561 this._overviewModeSetting.set(WebInspector.TimelinePanel.OverviewMode.Frames);
562 button.toggled = true;
564 this._overviewModeSetting.set(WebInspector.TimelinePanel.OverviewMode.Events);
565 button.toggled = false;
567 this._onModeChanged();
571 * @param {!WebInspector.StatusBarButton} button
573 _flameChartEnabledChanged: function(button)
575 var oldValue = this._flameChartEnabledSetting.get();
576 var newValue = !oldValue;
577 this._flameChartEnabledSetting.set(newValue);
578 button.toggled = newValue;
579 this._onModeChanged();
582 _onModeChanged: function()
584 this._stackView.detach();
586 var isFrameMode = this._overviewModeSetting.get() === WebInspector.TimelinePanel.OverviewMode.Frames;
587 this._removeAllModeViews();
588 this._overviewControls = [];
591 this._overviewControls.push(new WebInspector.TimelineFrameOverview(this._model, this._frameModel()));
593 this._overviewControls.push(new WebInspector.TimelineEventOverview(this._model, this._uiUtils));
595 if (this._tracingTimelineModel && this._flameChartEnabledSetting.get())
596 this._addModeView(new WebInspector.TimelineFlameChart(this, this._tracingTimelineModel, this._frameModel()));
598 this._addModeView(this._timelineView());
600 if (this._captureMemorySetting.get()) {
601 if (!isFrameMode) // Frame mode skews time, don't render aux overviews.
602 this._overviewControls.push(new WebInspector.TimelineMemoryOverview(this._model, this._uiUtils));
603 this._addModeView(new WebInspector.MemoryCountersGraph(this, this._model, this._uiUtils));
606 if (this._capturePowerSetting && this._capturePowerSetting.get() &&
607 WebInspector.targetManager.mainTarget().hasCapability(WebInspector.Target.Capabilities.CanProfilePower)) {
608 if (!isFrameMode) // Frame mode skews time, don't render aux overviews.
609 this._overviewControls.push(new WebInspector.TimelinePowerOverview(this._model));
610 this._addModeView(new WebInspector.TimelinePowerGraph(this, this._model));
613 if (this._lazyTimelineView)
614 this._lazyTimelineView.setFrameModel(isFrameMode ? this._frameModel() : null);
616 this._overviewPane.setOverviewControls(this._overviewControls);
618 this._updateSelectedRangeStats();
620 this._stackView.show(this._searchableView.element);
624 * @param {boolean} enabled
626 _setUIControlsEnabled: function(enabled) {
627 function handler(uiControl)
629 uiControl.checkbox.disabled = !enabled;
630 uiControl.label.classList.toggle("dimmed", !enabled);
632 this._recordingOptionUIControls.forEach(handler);
633 WebInspector.inspectorView.setCurrentPanelLocked(!enabled);
637 * @param {boolean} userInitiated
639 _startRecording: function(userInitiated)
641 this._userInitiatedRecording = userInitiated;
642 this._model.startRecording(this._captureStacksSetting.get(), this._captureMemorySetting.get(), this._captureLayersAndPicturesSetting && this._captureLayersAndPicturesSetting.get());
643 if (this._lazyFrameModel)
644 this._lazyFrameModel.setMergeRecords(false);
646 for (var i = 0; i < this._overviewControls.length; ++i)
647 this._overviewControls[i].timelineStarted();
650 WebInspector.userMetrics.TimelineStarted.record();
651 this._setUIControlsEnabled(false);
654 _stopRecording: function()
656 this._stopPending = true;
657 this._updateToggleTimelineButton(false);
658 this._userInitiatedRecording = false;
659 this._model.stopRecording();
660 if (this._progressElement)
661 this._updateProgress(WebInspector.UIString("Retrieving events\u2026"));
663 for (var i = 0; i < this._overviewControls.length; ++i)
664 this._overviewControls[i].timelineStopped();
665 this._setUIControlsEnabled(true);
668 _onProfilingStateChanged: function()
670 this._updateToggleTimelineButton(this.toggleTimelineButton.toggled);
674 * @param {boolean} toggled
676 _updateToggleTimelineButton: function(toggled)
678 var isAcquiredInSomeTarget = WebInspector.profilingLock().isAcquired();
679 this.toggleTimelineButton.toggled = toggled;
681 this.toggleTimelineButton.title = WebInspector.UIString("Stop");
682 this.toggleTimelineButton.setEnabled(true);
683 } else if (this._stopPending) {
684 this.toggleTimelineButton.title = WebInspector.UIString("Stop pending");
685 this.toggleTimelineButton.setEnabled(false);
686 } else if (isAcquiredInSomeTarget) {
687 this.toggleTimelineButton.title = WebInspector.anotherProfilerActiveLabel();
688 this.toggleTimelineButton.setEnabled(false);
690 this.toggleTimelineButton.title = WebInspector.UIString("Record");
691 this.toggleTimelineButton.setEnabled(true);
698 _toggleTimelineButtonClicked: function()
700 if (!this.toggleTimelineButton.enabled())
702 if (this._operationInProgress)
704 if (this._recordingInProgress())
705 this._stopRecording();
707 this._startRecording(true);
711 _garbageCollectButtonClicked: function()
713 var targets = WebInspector.targetManager.targets();
714 for (var i = 0; i < targets.length; ++i)
715 targets[i].heapProfilerAgent().collectGarbage();
718 _onClearButtonClick: function()
720 if (this._tracingModel)
721 this._tracingModel.reset();
725 _onRecordsCleared: function()
727 this.requestWindowTimes(0, Infinity);
728 delete this._selection;
729 if (this._lazyFrameModel)
730 this._lazyFrameModel.reset();
731 for (var i = 0; i < this._currentViews.length; ++i)
732 this._currentViews[i].reset();
733 for (var i = 0; i < this._overviewControls.length; ++i)
734 this._overviewControls[i].reset();
735 this._updateSelectedRangeStats();
738 _onRecordingStarted: function()
740 this._updateToggleTimelineButton(true);
741 this._updateProgress(WebInspector.UIString("%d events collected", 0));
744 _recordingInProgress: function()
746 return this.toggleTimelineButton.toggled;
750 * @param {!WebInspector.Event} event
752 _onRecordingProgress: function(event)
754 this._updateProgress(WebInspector.UIString("%d events collected", event.data));
758 * @param {!WebInspector.Event} event
760 _onTracingBufferUsage: function(event)
762 var usage = /** @type {number} */ (event.data);
763 this._updateProgress(WebInspector.UIString("Buffer usage %d%", Math.round(usage * 100)));
767 * @param {string} progressMessage
769 _updateProgress: function(progressMessage)
771 if (!this._progressElement)
772 this._showProgressPane();
773 this._progressElement.textContent = progressMessage;
776 _showProgressPane: function()
778 this._hideProgressPane();
779 this._progressElement = this._detailsSplitView.mainElement().createChild("div", "timeline-progress-pane");
782 _hideProgressPane: function()
784 if (this._progressElement)
785 this._progressElement.remove();
786 delete this._progressElement;
789 _onRecordingStopped: function()
791 this._stopPending = false;
792 this._updateToggleTimelineButton(false);
793 if (this._lazyFrameModel) {
794 this._lazyFrameModel.reset();
795 if (this._tracingTimelineModel)
796 this._lazyFrameModel.addTraceEvents(this._tracingTimelineModel.inspectedTargetEvents(), this._tracingModel.sessionId());
798 this._lazyFrameModel.addRecords(this._model.records());
800 if (this._tracingTimelineModel) {
801 this.requestWindowTimes(this._tracingTimelineModel.minimumRecordTime(), this._tracingTimelineModel.maximumRecordTime());
802 this._refreshViews();
804 this._hideProgressPane();
805 this._overviewPane.update();
808 _onRecordAdded: function(event)
810 this._addRecord(/** @type {!WebInspector.TimelineModel.Record} */(event.data));
814 * @param {!WebInspector.TimelineModel.Record} record
816 _addRecord: function(record)
818 if (this._lazyFrameModel && !this._tracingModel)
819 this._lazyFrameModel.addRecord(record);
820 for (var i = 0; i < this._currentViews.length; ++i)
821 this._currentViews[i].addRecord(record);
822 this._updateSearchHighlight(false, true);
826 * @param {!WebInspector.Event} event
828 _willReloadPage: function(event)
830 if (this._operationInProgress || this._userInitiatedRecording || !this.isShowing())
832 this._startRecording(false);
836 * @param {!WebInspector.Event} event
838 _loadEventFired: function(event)
840 if (!this._recordingInProgress() || this._userInitiatedRecording)
842 this._stopRecording();
845 // WebInspector.Searchable implementation
847 jumpToNextSearchResult: function()
849 if (!this._searchResults || !this._searchResults.length)
851 var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : -1;
852 this._jumpToSearchResult(index + 1);
855 jumpToPreviousSearchResult: function()
857 if (!this._searchResults || !this._searchResults.length)
859 var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : 0;
860 this._jumpToSearchResult(index - 1);
863 _jumpToSearchResult: function(index)
865 this._selectSearchResult((index + this._searchResults.length) % this._searchResults.length);
866 this._currentViews[0].highlightSearchResult(this._selectedSearchResult, this._searchRegex, true);
869 _selectSearchResult: function(index)
871 this._selectedSearchResult = this._searchResults[index];
872 this._searchableView.updateCurrentMatchIndex(index);
875 _clearHighlight: function()
877 this._currentViews[0].highlightSearchResult(null);
881 * @param {boolean} revealRecord
882 * @param {boolean} shouldJump
883 * @param {boolean=} jumpBackwards
885 _updateSearchHighlight: function(revealRecord, shouldJump, jumpBackwards)
887 if (!this._textFilter.isEmpty() || !this._searchRegex) {
888 this._clearHighlight();
892 if (!this._searchResults)
893 this._updateSearchResults(shouldJump, jumpBackwards);
894 this._currentViews[0].highlightSearchResult(this._selectedSearchResult, this._searchRegex, revealRecord);
898 * @param {boolean} shouldJump
899 * @param {boolean=} jumpBackwards
901 _updateSearchResults: function(shouldJump, jumpBackwards)
903 var searchRegExp = this._searchRegex;
910 * @param {!WebInspector.TimelineModel.Record} record
911 * @this {WebInspector.TimelinePanel}
913 function processRecord(record)
915 if (record.endTime() < this._windowStartTime ||
916 record.startTime() > this._windowEndTime)
918 if (this._uiUtils.testContentMatching(record, searchRegExp))
919 matches.push(record);
921 this._model.forAllFilteredRecords(processRecord.bind(this));
923 var matchesCount = matches.length;
925 this._searchResults = matches;
926 this._searchableView.updateSearchMatchesCount(matchesCount);
928 var selectedIndex = matches.indexOf(this._selectedSearchResult);
929 if (shouldJump && selectedIndex === -1)
930 selectedIndex = jumpBackwards ? this._searchResults.length - 1 : 0;
931 this._selectSearchResult(selectedIndex);
933 this._searchableView.updateSearchMatchesCount(0);
934 delete this._selectedSearchResult;
938 searchCanceled: function()
940 this._clearHighlight();
941 delete this._searchResults;
942 delete this._selectedSearchResult;
943 delete this._searchRegex;
947 * @param {string} query
948 * @param {boolean} shouldJump
949 * @param {boolean=} jumpBackwards
951 performSearch: function(query, shouldJump, jumpBackwards)
953 this._searchRegex = createPlainTextSearchRegex(query, "i");
954 delete this._searchResults;
955 this._updateSearchHighlight(true, shouldJump, jumpBackwards);
958 _updateSelectionDetails: function()
960 if (!this._selection) {
961 this._updateSelectedRangeStats();
964 switch (this._selection.type()) {
965 case WebInspector.TimelineSelection.Type.Record:
966 var record = /** @type {!WebInspector.TimelineModel.Record} */ (this._selection.object());
967 if (this._tracingTimelineModel) {
968 var event = record.traceEvent();
969 this._uiUtils.generateDetailsContent(record, this._model, this._detailsLinkifier, this._appendDetailsTabsForTraceEventAndShowDetails.bind(this, event));
972 var title = this._uiUtils.titleForRecord(record);
973 this._uiUtils.generateDetailsContent(record, this._model, this._detailsLinkifier, this.showInDetails.bind(this, title));
975 case WebInspector.TimelineSelection.Type.TraceEvent:
976 var event = /** @type {!WebInspector.TracingModel.Event} */ (this._selection.object());
977 WebInspector.TracingTimelineUIUtils.buildTraceEventDetails(event, this._tracingTimelineModel, this._detailsLinkifier, this._appendDetailsTabsForTraceEventAndShowDetails.bind(this, event));
979 case WebInspector.TimelineSelection.Type.Frame:
980 var frame = /** @type {!WebInspector.TimelineFrame} */ (this._selection.object());
981 this.showInDetails(WebInspector.UIString("Frame"), WebInspector.TimelineUIUtils.generateDetailsContentForFrame(this._lazyFrameModel, frame));
982 if (frame.layerTree) {
983 var layersView = this._layersView();
984 layersView.showLayerTree(frame.layerTree, frame.paints);
985 this._detailsView.appendTab("layers", WebInspector.UIString("Layers"), layersView);
992 * @param {!WebInspector.TracingModel.Event} event
993 * @param {!Node} content
995 _appendDetailsTabsForTraceEventAndShowDetails: function(event, content)
997 var title = WebInspector.TracingTimelineUIUtils.eventStyle(event).title;
998 this.showInDetails(title, content);
1001 var paintProfilerView = this._paintProfilerView();
1002 this._detailsView.appendTab("paintProfiler", WebInspector.UIString("Paint Profiler"), paintProfilerView);
1003 event.picture.requestObject(onGotObject);
1004 function onGotObject(result)
1006 if (!result || !result["skp64"])
1008 paintProfilerView.setPicture(event.thread.target(), result["skp64"]);
1012 _updateSelectedRangeStats: function()
1014 if (this._selection)
1017 var startTime = this._windowStartTime;
1018 var endTime = this._windowEndTime;
1019 var uiUtils = this._uiUtils;
1021 // Return early in case 0 selection window.
1025 var aggregatedStats = {};
1028 * @param {number} value
1029 * @param {!WebInspector.TimelineModel.Record} task
1032 function compareEndTime(value, task)
1034 return value < task.endTime() ? -1 : 1;
1038 * @param {!WebInspector.TimelineModel.Record} record
1040 function aggregateTimeForRecordWithinWindow(record)
1042 if (!record.endTime() || record.endTime() < startTime || record.startTime() > endTime)
1045 var childrenTime = 0;
1046 var children = record.children() || [];
1047 for (var i = 0; i < children.length; ++i) {
1048 var child = children[i];
1049 if (!child.endTime() || child.endTime() < startTime || child.startTime() > endTime)
1051 childrenTime += Math.min(endTime, child.endTime()) - Math.max(startTime, child.startTime());
1052 aggregateTimeForRecordWithinWindow(child);
1054 var categoryName = uiUtils.categoryForRecord(record).name;
1055 var ownTime = Math.min(endTime, record.endTime()) - Math.max(startTime, record.startTime()) - childrenTime;
1056 aggregatedStats[categoryName] = (aggregatedStats[categoryName] || 0) + ownTime;
1059 var mainThreadTasks = this._model.mainThreadTasks();
1060 var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, mainThreadTasks, compareEndTime);
1061 for (; taskIndex < mainThreadTasks.length; ++taskIndex) {
1062 var task = mainThreadTasks[taskIndex];
1063 if (task.startTime() > endTime)
1065 aggregateTimeForRecordWithinWindow(task);
1068 var aggregatedTotal = 0;
1069 for (var categoryName in aggregatedStats)
1070 aggregatedTotal += aggregatedStats[categoryName];
1071 aggregatedStats["idle"] = Math.max(0, endTime - startTime - aggregatedTotal);
1073 var pieChartContainer = document.createElement("div");
1074 pieChartContainer.classList.add("vbox", "timeline-range-summary");
1075 var startOffset = startTime - this._model.minimumRecordTime();
1076 var endOffset = endTime - this._model.minimumRecordTime();
1077 var title = WebInspector.UIString("Range: %s \u2013 %s", Number.millisToString(startOffset), Number.millisToString(endOffset));
1079 for (var i = 0; i < this._overviewControls.length; ++i) {
1080 if (this._overviewControls[i] instanceof WebInspector.TimelinePowerOverview) {
1081 var energy = this._overviewControls[i].calculateEnergy(startTime, endTime);
1082 title += WebInspector.UIString(" Energy: %.2f Joules", energy);
1083 title += WebInspector.UIString(" Accuracy: %s", WebInspector.powerProfiler.getAccuracyLevel());
1087 pieChartContainer.createChild("div").textContent = title;
1088 pieChartContainer.appendChild(WebInspector.TimelineUIUtils.generatePieChart(aggregatedStats));
1089 this.showInDetails(WebInspector.UIString("Selected Range"), pieChartContainer);
1093 * @param {?WebInspector.TimelineSelection} selection
1095 select: function(selection)
1097 this._detailsLinkifier.reset();
1098 this._selection = selection;
1100 for (var i = 0; i < this._currentViews.length; ++i) {
1101 var view = this._currentViews[i];
1102 view.setSelection(selection);
1104 this._updateSelectionDetails();
1108 * @param {string} title
1109 * @param {!Node} node
1111 showInDetails: function(title, node)
1113 this._detailsView.setContent(title, node);
1116 __proto__: WebInspector.Panel.prototype
1121 * @extends {WebInspector.TabbedPane}
1123 WebInspector.TimelineDetailsView = function()
1125 WebInspector.TabbedPane.call(this);
1127 this._recordTitleElement = document.createElement("div");
1128 this._recordTitleElement.classList.add("record-title");
1129 this.headerElement().insertBefore(this._recordTitleElement, this.headerElement().firstChild)
1130 this._defaultDetailsView = new WebInspector.VBox();
1131 this._defaultDetailsView.element.classList.add("timeline-details-view");
1132 this._defaultDetailsContentElement = this._defaultDetailsView.element.createChild("div", "timeline-details-view-body");
1134 this.appendTab("default", WebInspector.UIString("Details"), this._defaultDetailsView);
1136 this.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
1139 WebInspector.TimelineDetailsView.prototype = {
1141 * @param {string} title
1142 * @param {!Node} node
1144 setContent: function(title, node)
1146 this._recordTitleElement.textContent = title;
1147 var otherTabs = this.otherTabs("default");
1148 for (var i = 0; i < otherTabs.length; ++i)
1149 this.closeTab(otherTabs[i]);
1150 this._defaultDetailsContentElement.removeChildren();
1151 this._defaultDetailsContentElement.appendChild(node);
1155 * @param {boolean} vertical
1157 setVertical: function(vertical)
1159 this._defaultDetailsContentElement.classList.toggle("hbox", !vertical);
1160 this._defaultDetailsContentElement.classList.toggle("vbox", vertical);
1165 * @param {string} id
1166 * @param {string} tabTitle
1167 * @param {!WebInspector.View} view
1168 * @param {string=} tabTooltip
1169 * @param {boolean=} userGesture
1170 * @param {boolean=} isCloseable
1172 appendTab: function(id, tabTitle, view, tabTooltip, userGesture, isCloseable)
1174 WebInspector.TabbedPane.prototype.appendTab.call(this, id, tabTitle, view, tabTooltip);
1175 if (this._lastUserSelectedTabId !== this.selectedTabId)
1179 _tabSelected: function(event)
1181 if (!event.data.isUserGesture)
1184 this._lastUserSelectedTabId = event.data.tabId;
1187 __proto__: WebInspector.TabbedPane.prototype
1193 WebInspector.TimelineSelection = function()
1200 WebInspector.TimelineSelection.Type = {
1203 TraceEvent: "TraceEvent",
1207 * @param {!WebInspector.TimelineModel.Record} record
1208 * @return {!WebInspector.TimelineSelection}
1210 WebInspector.TimelineSelection.fromRecord = function(record)
1212 var selection = new WebInspector.TimelineSelection();
1213 selection._type = WebInspector.TimelineSelection.Type.Record;
1214 selection._object = record;
1219 * @param {!WebInspector.TimelineFrame} frame
1220 * @return {!WebInspector.TimelineSelection}
1222 WebInspector.TimelineSelection.fromFrame = function(frame)
1224 var selection = new WebInspector.TimelineSelection();
1225 selection._type = WebInspector.TimelineSelection.Type.Frame;
1226 selection._object = frame;
1231 * @param {!WebInspector.TracingModel.Event} event
1232 * @return {!WebInspector.TimelineSelection}
1234 WebInspector.TimelineSelection.fromTraceEvent = function(event)
1236 var selection = new WebInspector.TimelineSelection();
1237 selection._type = WebInspector.TimelineSelection.Type.TraceEvent;
1238 selection._object = event;
1242 WebInspector.TimelineSelection.prototype = {
1244 * @return {!WebInspector.TimelineSelection.Type}
1256 return this._object;
1262 * @extends {WebInspector.EventTarget}
1264 WebInspector.TimelineModeView = function()
1268 WebInspector.TimelineModeView.prototype = {
1270 * @return {!WebInspector.View}
1272 view: function() {},
1274 dispose: function() {},
1276 reset: function() {},
1279 * @param {?RegExp} textFilter
1281 refreshRecords: function(textFilter) {},
1284 * @param {!WebInspector.TimelineModel.Record} record
1286 addRecord: function(record) {},
1289 * @param {?WebInspector.TimelineModel.Record} record
1290 * @param {string=} regex
1291 * @param {boolean=} selectRecord
1293 highlightSearchResult: function(record, regex, selectRecord) {},
1296 * @param {number} startTime
1297 * @param {number} endTime
1299 setWindowTimes: function(startTime, endTime) {},
1302 * @param {number} width
1304 setSidebarSize: function(width) {},
1307 * @param {?WebInspector.TimelineSelection} selection
1309 setSelection: function(selection) {},
1315 WebInspector.TimelineModeViewDelegate = function() {}
1317 WebInspector.TimelineModeViewDelegate.prototype = {
1319 * @param {number} startTime
1320 * @param {number} endTime
1322 requestWindowTimes: function(startTime, endTime) {},
1325 * @param {?WebInspector.TimelineSelection} selection
1327 select: function(selection) {},
1330 * @param {string} title
1331 * @param {!Node} node
1333 showInDetails: function(title, node) {},
1338 * @extends {WebInspector.TimelineModel.Filter}
1339 * @param {!WebInspector.TimelineUIUtils} uiUtils
1341 WebInspector.TimelineCategoryFilter = function(uiUtils)
1343 WebInspector.TimelineModel.Filter.call(this);
1344 this._uiUtils = uiUtils;
1347 WebInspector.TimelineCategoryFilter.prototype = {
1349 * @param {!WebInspector.TimelineModel.Record} record
1352 accept: function(record)
1354 return !this._uiUtils.categoryForRecord(record).hidden;
1357 __proto__: WebInspector.TimelineModel.Filter.prototype
1362 * @extends {WebInspector.TimelineModel.Filter}
1364 WebInspector.TimelineIsLongFilter = function()
1366 WebInspector.TimelineModel.Filter.call(this);
1367 this._minimumRecordDuration = 0;
1370 WebInspector.TimelineIsLongFilter.prototype = {
1372 * @param {number} value
1374 setMinimumRecordDuration: function(value)
1376 this._minimumRecordDuration = value;
1377 this.notifyFilterChanged();
1381 * @param {!WebInspector.TimelineModel.Record} record
1384 accept: function(record)
1386 return this._minimumRecordDuration ? ((record.endTime() - record.startTime()) >= this._minimumRecordDuration) : true;
1389 __proto__: WebInspector.TimelineModel.Filter.prototype
1395 * @extends {WebInspector.TimelineModel.Filter}
1396 * @param {!WebInspector.TimelineUIUtils} uiUtils
1398 WebInspector.TimelineTextFilter = function(uiUtils)
1400 WebInspector.TimelineModel.Filter.call(this);
1401 this._uiUtils = uiUtils;
1404 WebInspector.TimelineTextFilter.prototype = {
1410 return !this._regex;
1414 * @param {?RegExp} regex
1416 setRegex: function(regex)
1418 this._regex = regex;
1419 this.notifyFilterChanged();
1423 * @param {!WebInspector.TimelineModel.Record} record
1426 accept: function(record)
1428 return !this._regex || this._uiUtils.testContentMatching(record, this._regex);
1431 __proto__: WebInspector.TimelineModel.Filter.prototype