1/*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 * Copyright (C) 2012 Intel Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
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
14 * distribution.
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.
18 *
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.
30 */
31
32/**
33 * @constructor
34 * @extends {WebInspector.Panel}
35 * @implements {WebInspector.TimelineModeViewDelegate}
36 * @implements {WebInspector.Searchable}
37 */
38WebInspector.TimelinePanel = function()
39{
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);
45
46    this._detailsLinkifier = new WebInspector.Linkifier();
47    this._windowStartTime = 0;
48    this._windowEndTime = Infinity;
49
50    // Create model.
51    if (Runtime.experiments.isEnabled("timelineOnTraceEvents")) {
52        this._tracingManager = new WebInspector.TracingManager();
53        this._tracingManager.addEventListener(WebInspector.TracingManager.Events.BufferUsage, this._onTracingBufferUsage, this);
54
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;
59    } else {
60        this._uiUtils = new WebInspector.TimelineUIUtilsImpl();
61        this._model = new WebInspector.TimelineModelImpl();
62    }
63
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);
70
71    this._categoryFilter = new WebInspector.TimelineCategoryFilter(this._uiUtils);
72    this._durationFilter = new WebInspector.TimelineIsLongFilter();
73    this._textFilter = new WebInspector.TimelineTextFilter(this._uiUtils);
74
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);
82
83    /** @type {!Array.<!WebInspector.TimelineModeView>} */
84    this._currentViews = [];
85
86    this._overviewModeSetting = WebInspector.settings.createSetting("timelineOverviewMode", WebInspector.TimelinePanel.OverviewMode.Events);
87    this._flameChartEnabledSetting = WebInspector.settings.createSetting("timelineFlameChartEnabled", false);
88    this._createStatusBarItems();
89
90    var topPaneElement = this.element.createChild("div", "hbox");
91    topPaneElement.id = "timeline-overview-panel";
92
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);
97
98    this._createFileSelector();
99    this._registerShortcuts();
100
101    WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.WillReloadPage, this._willReloadPage, this);
102    WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.Load, this._loadEventFired, this);
103
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());
111
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());
116
117    this._stackView = new WebInspector.StackView(false);
118    this._stackView.show(this._searchableView.element);
119    this._stackView.element.classList.add("timeline-view-stack");
120
121    WebInspector.dockController.addEventListener(WebInspector.DockController.Events.DockSideChanged, this._dockSideChanged.bind(this));
122    WebInspector.settings.splitVerticallyWhenDockedToRight.addChangeListener(this._dockSideChanged.bind(this));
123    this._dockSideChanged();
124
125    this._onModeChanged();
126    this._detailsSplitView.show(this.element);
127    WebInspector.profilingLock().addEventListener(WebInspector.Lock.Events.StateChanged, this._onProfilingStateChanged, this);
128}
129
130WebInspector.TimelinePanel.OverviewMode = {
131    Events: "Events",
132    Frames: "Frames"
133};
134
135// Define row and header height, should be in sync with styles for timeline graphs.
136WebInspector.TimelinePanel.rowHeight = 18;
137WebInspector.TimelinePanel.headerHeight = 20;
138
139WebInspector.TimelinePanel.durationFilterPresetsMs = [0, 1, 15];
140
141WebInspector.TimelinePanel.prototype = {
142    /**
143     * @return {?WebInspector.SearchableView}
144     */
145    searchableView: function()
146    {
147        return this._searchableView;
148    },
149
150    wasShown: function()
151    {
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);
158        }
159    },
160
161    _dockSideChanged: function()
162    {
163        var dockSide = WebInspector.dockController.dockSide();
164        var vertically = false;
165        if (dockSide === WebInspector.DockController.State.DockedToBottom)
166            vertically = true;
167        else
168            vertically = !WebInspector.settings.splitVerticallyWhenDockedToRight.get();
169        this._detailsSplitView.setVertical(vertically);
170        this._detailsView.setVertical(vertically);
171    },
172
173    /**
174     * @return {number}
175     */
176    windowStartTime: function()
177    {
178        if (this._windowStartTime)
179            return this._windowStartTime;
180        return this._model.minimumRecordTime();
181    },
182
183    /**
184     * @return {number}
185     */
186    windowEndTime: function()
187    {
188        if (this._windowEndTime < Infinity)
189            return this._windowEndTime;
190        return this._model.maximumRecordTime() || Infinity;
191    },
192
193    /**
194     * @param {!WebInspector.Event} event
195     */
196    _sidebarResized: function(event)
197    {
198        var width = /** @type {number} */ (event.data);
199        for (var i = 0; i < this._currentViews.length; ++i)
200            this._currentViews[i].setSidebarSize(width);
201    },
202
203    /**
204     * @param {!WebInspector.Event} event
205     */
206    _onWindowChanged: function(event)
207    {
208        this._windowStartTime = event.data.startTime;
209        this._windowEndTime = event.data.endTime;
210
211        for (var i = 0; i < this._currentViews.length; ++i)
212            this._currentViews[i].setWindowTimes(this._windowStartTime, this._windowEndTime);
213    },
214
215    /**
216     * @param {number} windowStartTime
217     * @param {number} windowEndTime
218     */
219    requestWindowTimes: function(windowStartTime, windowEndTime)
220    {
221        this._overviewPane.requestWindowTimes(windowStartTime, windowEndTime);
222    },
223
224    /**
225     * @return {!WebInspector.TimelineFrameModelBase}
226     */
227    _frameModel: function()
228    {
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;
235        } else {
236            var frameModel = new WebInspector.TimelineFrameModel();
237            frameModel.addRecords(this._model.records());
238            this._lazyFrameModel = frameModel;
239        }
240        return this._lazyFrameModel;
241    },
242
243    /**
244     * @return {!WebInspector.TimelineView}
245     */
246    _timelineView: function()
247    {
248        if (!this._lazyTimelineView)
249            this._lazyTimelineView = new WebInspector.TimelineView(this, this._model, this._uiUtils);
250        return this._lazyTimelineView;
251    },
252
253    /**
254     * @return {!WebInspector.View}
255     */
256    _layersView: function()
257    {
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;
263    },
264
265    _paintProfilerView: function()
266    {
267        if (this._lazyPaintProfilerView)
268            return this._lazyPaintProfilerView;
269        this._lazyPaintProfilerView = new WebInspector.TimelinePaintProfilerView();
270        return this._lazyPaintProfilerView;
271    },
272
273    /**
274     * @param {!WebInspector.TimelineModeView} modeView
275     */
276    _addModeView: function(modeView)
277    {
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);
283    },
284
285    _removeAllModeViews: function()
286    {
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();
290        }
291        this._currentViews = [];
292        this._stackView.detachChildViews();
293    },
294
295    /**
296     * @param {string} name
297     * @param {!WebInspector.Setting} setting
298     * @param {string} tooltip
299     * @return {!Element}
300     */
301    _createSettingCheckbox: function(name, setting, tooltip)
302    {
303        if (!this._recordingOptionUIControls)
304            this._recordingOptionUIControls = [];
305
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 });
309        return labelElement;
310    },
311
312    _createStatusBarItems: function()
313    {
314        var panelStatusBarElement = this.element.createChild("div", "panel-status-bar");
315        this._statusBarButtons = /** @type {!Array.<!WebInspector.StatusBarItem>} */ ([]);
316
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);
322
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);
327
328        this._filterBar = this._createFilterBar();
329        panelStatusBarElement.appendChild(this._filterBar.filterButton().element);
330
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);
335
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);
341
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);
348        }
349
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);
367        }
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")));
373        }
374
375        this._miscStatusBarItems = panelStatusBarElement.createChild("div", "status-bar-item");
376
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);
385        }
386    },
387
388    /**
389     * @return {!WebInspector.FilterBar}
390     */
391    _createFilterBar: function()
392    {
393        this._filterBar = new WebInspector.FilterBar();
394        this._filters = {};
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);
398
399        var durationOptions = [];
400        for (var presetIndex = 0; presetIndex < WebInspector.TimelinePanel.durationFilterPresetsMs.length; ++presetIndex) {
401            var durationMs = WebInspector.TimelinePanel.durationFilterPresetsMs[presetIndex];
402            var durationOption = {};
403            if (!durationMs) {
404                durationOption.label = WebInspector.UIString("All");
405                durationOption.title = WebInspector.UIString("Show all records");
406            } else {
407                durationOption.label = WebInspector.UIString("\u2265 %dms", durationMs);
408                durationOption.title = WebInspector.UIString("Hide records shorter than %dms", durationMs);
409            }
410            durationOption.value = durationMs;
411            durationOptions.push(durationOption);
412        }
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);
416
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)
423                continue;
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);
428        }
429        return this._filterBar;
430    },
431
432    _textFilterChanged: function(event)
433    {
434        var searchQuery = this._filters._textFilterUI.value();
435        this.searchCanceled();
436        this._textFilter.setRegex(searchQuery ? createPlainTextSearchRegex(searchQuery, "i") : null);
437    },
438
439    _durationFilterChanged: function()
440    {
441        var duration = this._filters._durationFilterUI.value();
442        var minimumRecordDuration = parseInt(duration, 10);
443        this._durationFilter.setMinimumRecordDuration(minimumRecordDuration);
444    },
445
446    _categoriesFilterChanged: function(name, event)
447    {
448        var categories = WebInspector.TimelineUIUtils.categories();
449        categories[name].hidden = !this._filters._categoryFiltersUI[name].checked();
450        this._categoryFilter.notifyFilterChanged();
451    },
452
453    _onFiltersToggled: function(event)
454    {
455        var toggled = /** @type {boolean} */ (event.data);
456        this._filtersContainer.classList.toggle("hidden", !toggled);
457        this.doResize();
458    },
459
460    /**
461     * @return {?WebInspector.ProgressIndicator}
462     */
463    _prepareToLoadTimeline: function()
464    {
465        if (this._operationInProgress)
466            return null;
467        if (this._recordingInProgress()) {
468            this._updateToggleTimelineButton(false);
469            this._stopRecording();
470        }
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;
475    },
476
477    /**
478     * @param {?WebInspector.ProgressIndicator} indicator
479     */
480    _setOperationInProgress: function(indicator)
481    {
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();
486        if (indicator)
487            this._miscStatusBarItems.appendChild(indicator.element);
488    },
489
490    _registerShortcuts: function()
491    {
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));
495    },
496
497    _createFileSelector: function()
498    {
499        if (this._fileSelectorElement)
500            this._fileSelectorElement.remove();
501
502        this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this));
503        this.element.appendChild(this._fileSelectorElement);
504    },
505
506    _contextMenu: function(event)
507    {
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);
511        contextMenu.show();
512    },
513
514    /**
515     * @return {boolean}
516     */
517    _saveToFile: function()
518    {
519        if (this._operationInProgress)
520            return true;
521        this._model.saveToFile();
522        return true;
523    },
524
525    /**
526     * @return {boolean}
527     */
528    _selectFileToLoad: function() {
529        this._fileSelectorElement.click();
530        return true;
531    },
532
533    /**
534     * @param {!File} file
535     */
536    _loadFromFile: function(file)
537    {
538        var progressIndicator = this._prepareToLoadTimeline();
539        if (!progressIndicator)
540            return;
541        this._model.loadFromFile(file, progressIndicator);
542        this._createFileSelector();
543    },
544
545    _refreshViews: function()
546    {
547        for (var i = 0; i < this._currentViews.length; ++i) {
548            var view = this._currentViews[i];
549            view.refreshRecords(this._textFilter._regex);
550        }
551        this._updateSelectedRangeStats();
552    },
553
554    /**
555     * @param {!WebInspector.StatusBarButton} button
556     */
557    _overviewModeChanged: function(button)
558    {
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;
563        } else {
564            this._overviewModeSetting.set(WebInspector.TimelinePanel.OverviewMode.Events);
565            button.toggled = false;
566        }
567        this._onModeChanged();
568    },
569
570    /**
571     * @param {!WebInspector.StatusBarButton} button
572     */
573    _flameChartEnabledChanged: function(button)
574    {
575        var oldValue = this._flameChartEnabledSetting.get();
576        var newValue = !oldValue;
577        this._flameChartEnabledSetting.set(newValue);
578        button.toggled = newValue;
579        this._onModeChanged();
580    },
581
582    _onModeChanged: function()
583    {
584        this._stackView.detach();
585
586        var isFrameMode = this._overviewModeSetting.get() === WebInspector.TimelinePanel.OverviewMode.Frames;
587        this._removeAllModeViews();
588        this._overviewControls = [];
589
590        if (isFrameMode)
591            this._overviewControls.push(new WebInspector.TimelineFrameOverview(this._model, this._frameModel()));
592        else
593            this._overviewControls.push(new WebInspector.TimelineEventOverview(this._model, this._uiUtils));
594
595        if (this._tracingTimelineModel && this._flameChartEnabledSetting.get())
596            this._addModeView(new WebInspector.TimelineFlameChart(this, this._tracingTimelineModel, this._frameModel()));
597        else
598            this._addModeView(this._timelineView());
599
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));
604        }
605
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));
611        }
612
613        if (this._lazyTimelineView)
614            this._lazyTimelineView.setFrameModel(isFrameMode ? this._frameModel() : null);
615
616        this._overviewPane.setOverviewControls(this._overviewControls);
617        this.doResize();
618        this._updateSelectedRangeStats();
619
620        this._stackView.show(this._searchableView.element);
621    },
622
623    /**
624     * @param {boolean} enabled
625     */
626    _setUIControlsEnabled: function(enabled) {
627        function handler(uiControl)
628        {
629            uiControl.checkbox.disabled = !enabled;
630            uiControl.label.classList.toggle("dimmed", !enabled);
631        }
632        this._recordingOptionUIControls.forEach(handler);
633        WebInspector.inspectorView.setCurrentPanelLocked(!enabled);
634    },
635
636    /**
637     * @param {boolean} userInitiated
638     */
639    _startRecording: function(userInitiated)
640    {
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);
645
646        for (var i = 0; i < this._overviewControls.length; ++i)
647            this._overviewControls[i].timelineStarted();
648
649        if (userInitiated)
650            WebInspector.userMetrics.TimelineStarted.record();
651        this._setUIControlsEnabled(false);
652    },
653
654    _stopRecording: function()
655    {
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"));
662
663        for (var i = 0; i < this._overviewControls.length; ++i)
664            this._overviewControls[i].timelineStopped();
665        this._setUIControlsEnabled(true);
666    },
667
668    _onProfilingStateChanged: function()
669    {
670        this._updateToggleTimelineButton(this.toggleTimelineButton.toggled);
671    },
672
673    /**
674     * @param {boolean} toggled
675     */
676    _updateToggleTimelineButton: function(toggled)
677    {
678        var isAcquiredInSomeTarget = WebInspector.profilingLock().isAcquired();
679        this.toggleTimelineButton.toggled = toggled;
680        if (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);
689        } else {
690            this.toggleTimelineButton.title = WebInspector.UIString("Record");
691            this.toggleTimelineButton.setEnabled(true);
692        }
693    },
694
695    /**
696     * @return {boolean}
697     */
698    _toggleTimelineButtonClicked: function()
699    {
700        if (!this.toggleTimelineButton.enabled())
701            return true;
702        if (this._operationInProgress)
703            return true;
704        if (this._recordingInProgress())
705            this._stopRecording();
706        else
707            this._startRecording(true);
708        return true;
709    },
710
711    _garbageCollectButtonClicked: function()
712    {
713        var targets = WebInspector.targetManager.targets();
714        for (var i = 0; i < targets.length; ++i)
715            targets[i].heapProfilerAgent().collectGarbage();
716    },
717
718    _onClearButtonClick: function()
719    {
720        if (this._tracingModel)
721            this._tracingModel.reset();
722        this._model.reset();
723    },
724
725    _onRecordsCleared: function()
726    {
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();
736    },
737
738    _onRecordingStarted: function()
739    {
740        this._updateToggleTimelineButton(true);
741        this._updateProgress(WebInspector.UIString("%d events collected", 0));
742    },
743
744    _recordingInProgress: function()
745    {
746        return this.toggleTimelineButton.toggled;
747    },
748
749    /**
750     * @param {!WebInspector.Event} event
751     */
752    _onRecordingProgress: function(event)
753    {
754        this._updateProgress(WebInspector.UIString("%d events collected", event.data));
755    },
756
757    /**
758     * @param {!WebInspector.Event} event
759     */
760    _onTracingBufferUsage: function(event)
761    {
762        var usage = /** @type {number} */ (event.data);
763        this._updateProgress(WebInspector.UIString("Buffer usage %d%", Math.round(usage * 100)));
764    },
765
766    /**
767     * @param {string} progressMessage
768     */
769    _updateProgress: function(progressMessage)
770    {
771        if (!this._progressElement)
772            this._showProgressPane();
773        this._progressElement.textContent = progressMessage;
774    },
775
776    _showProgressPane: function()
777    {
778        this._hideProgressPane();
779        this._progressElement = this._detailsSplitView.mainElement().createChild("div", "timeline-progress-pane");
780    },
781
782    _hideProgressPane: function()
783    {
784        if (this._progressElement)
785            this._progressElement.remove();
786        delete this._progressElement;
787    },
788
789    _onRecordingStopped: function()
790    {
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());
797            else
798                this._lazyFrameModel.addRecords(this._model.records());
799        }
800        if (this._tracingTimelineModel) {
801            this.requestWindowTimes(this._tracingTimelineModel.minimumRecordTime(), this._tracingTimelineModel.maximumRecordTime());
802            this._refreshViews();
803        }
804        this._hideProgressPane();
805        this._overviewPane.update();
806    },
807
808    _onRecordAdded: function(event)
809    {
810        this._addRecord(/** @type {!WebInspector.TimelineModel.Record} */(event.data));
811    },
812
813    /**
814     * @param {!WebInspector.TimelineModel.Record} record
815     */
816    _addRecord: function(record)
817    {
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);
823    },
824
825    /**
826     * @param {!WebInspector.Event} event
827     */
828    _willReloadPage: function(event)
829    {
830        if (this._operationInProgress || this._userInitiatedRecording || !this.isShowing())
831            return;
832        this._startRecording(false);
833    },
834
835    /**
836     * @param {!WebInspector.Event} event
837     */
838    _loadEventFired: function(event)
839    {
840        if (!this._recordingInProgress() || this._userInitiatedRecording)
841            return;
842        this._stopRecording();
843    },
844
845    // WebInspector.Searchable implementation
846
847    jumpToNextSearchResult: function()
848    {
849        if (!this._searchResults || !this._searchResults.length)
850            return;
851        var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : -1;
852        this._jumpToSearchResult(index + 1);
853    },
854
855    jumpToPreviousSearchResult: function()
856    {
857        if (!this._searchResults || !this._searchResults.length)
858            return;
859        var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : 0;
860        this._jumpToSearchResult(index - 1);
861    },
862
863    _jumpToSearchResult: function(index)
864    {
865        this._selectSearchResult((index + this._searchResults.length) % this._searchResults.length);
866        this._currentViews[0].highlightSearchResult(this._selectedSearchResult, this._searchRegex, true);
867    },
868
869    _selectSearchResult: function(index)
870    {
871        this._selectedSearchResult = this._searchResults[index];
872        this._searchableView.updateCurrentMatchIndex(index);
873    },
874
875    _clearHighlight: function()
876    {
877        this._currentViews[0].highlightSearchResult(null);
878    },
879
880    /**
881     * @param {boolean} revealRecord
882     * @param {boolean} shouldJump
883     * @param {boolean=} jumpBackwards
884     */
885    _updateSearchHighlight: function(revealRecord, shouldJump, jumpBackwards)
886    {
887        if (!this._textFilter.isEmpty() || !this._searchRegex) {
888            this._clearHighlight();
889            return;
890        }
891
892        if (!this._searchResults)
893            this._updateSearchResults(shouldJump, jumpBackwards);
894        this._currentViews[0].highlightSearchResult(this._selectedSearchResult, this._searchRegex, revealRecord);
895    },
896
897    /**
898     * @param {boolean} shouldJump
899     * @param {boolean=} jumpBackwards
900     */
901    _updateSearchResults: function(shouldJump, jumpBackwards)
902    {
903        var searchRegExp = this._searchRegex;
904        if (!searchRegExp)
905            return;
906
907        var matches = [];
908
909        /**
910         * @param {!WebInspector.TimelineModel.Record} record
911         * @this {WebInspector.TimelinePanel}
912         */
913        function processRecord(record)
914        {
915            if (record.endTime() < this._windowStartTime ||
916                record.startTime() > this._windowEndTime)
917                return;
918            if (this._uiUtils.testContentMatching(record, searchRegExp))
919                matches.push(record);
920        }
921        this._model.forAllFilteredRecords(processRecord.bind(this));
922
923        var matchesCount = matches.length;
924        if (matchesCount) {
925            this._searchResults = matches;
926            this._searchableView.updateSearchMatchesCount(matchesCount);
927
928            var selectedIndex = matches.indexOf(this._selectedSearchResult);
929            if (shouldJump && selectedIndex === -1)
930                selectedIndex = jumpBackwards ? this._searchResults.length - 1 : 0;
931            this._selectSearchResult(selectedIndex);
932        } else {
933            this._searchableView.updateSearchMatchesCount(0);
934            delete this._selectedSearchResult;
935        }
936    },
937
938    searchCanceled: function()
939    {
940        this._clearHighlight();
941        delete this._searchResults;
942        delete this._selectedSearchResult;
943        delete this._searchRegex;
944    },
945
946    /**
947     * @param {string} query
948     * @param {boolean} shouldJump
949     * @param {boolean=} jumpBackwards
950     */
951    performSearch: function(query, shouldJump, jumpBackwards)
952    {
953        this._searchRegex = createPlainTextSearchRegex(query, "i");
954        delete this._searchResults;
955        this._updateSearchHighlight(true, shouldJump, jumpBackwards);
956    },
957
958    _updateSelectionDetails: function()
959    {
960        if (!this._selection) {
961            this._updateSelectedRangeStats();
962            return;
963        }
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));
970                break;
971            }
972            var title = this._uiUtils.titleForRecord(record);
973            this._uiUtils.generateDetailsContent(record, this._model, this._detailsLinkifier, this.showInDetails.bind(this, title));
974            break;
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));
978            break;
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);
986            }
987            break;
988        }
989    },
990
991    /**
992     * @param {!WebInspector.TracingModel.Event} event
993     * @param {!Node} content
994     */
995    _appendDetailsTabsForTraceEventAndShowDetails: function(event, content)
996    {
997        var title = WebInspector.TracingTimelineUIUtils.eventStyle(event).title;
998        this.showInDetails(title, content);
999        if (!event.picture)
1000            return;
1001        var paintProfilerView = this._paintProfilerView();
1002        this._detailsView.appendTab("paintProfiler", WebInspector.UIString("Paint Profiler"), paintProfilerView);
1003        event.picture.requestObject(onGotObject);
1004        function onGotObject(result)
1005        {
1006            if (!result || !result["skp64"])
1007                return;
1008            paintProfilerView.setPicture(event.thread.target(), result["skp64"]);
1009        }
1010    },
1011
1012    _updateSelectedRangeStats: function()
1013    {
1014        if (this._selection)
1015            return;
1016
1017        var startTime = this._windowStartTime;
1018        var endTime = this._windowEndTime;
1019        var uiUtils = this._uiUtils;
1020
1021        // Return early in case 0 selection window.
1022        if (startTime < 0)
1023            return;
1024
1025        var aggregatedStats = {};
1026
1027        /**
1028         * @param {number} value
1029         * @param {!WebInspector.TimelineModel.Record} task
1030         * @return {number}
1031         */
1032        function compareEndTime(value, task)
1033        {
1034            return value < task.endTime() ? -1 : 1;
1035        }
1036
1037        /**
1038         * @param {!WebInspector.TimelineModel.Record} record
1039         */
1040        function aggregateTimeForRecordWithinWindow(record)
1041        {
1042            if (!record.endTime() || record.endTime() < startTime || record.startTime() > endTime)
1043                return;
1044
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)
1050                    continue;
1051                childrenTime += Math.min(endTime, child.endTime()) - Math.max(startTime, child.startTime());
1052                aggregateTimeForRecordWithinWindow(child);
1053            }
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;
1057        }
1058
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)
1064                break;
1065            aggregateTimeForRecordWithinWindow(task);
1066        }
1067
1068        var aggregatedTotal = 0;
1069        for (var categoryName in aggregatedStats)
1070            aggregatedTotal += aggregatedStats[categoryName];
1071        aggregatedStats["idle"] = Math.max(0, endTime - startTime - aggregatedTotal);
1072
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));
1078
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());
1084                break;
1085            }
1086        }
1087        pieChartContainer.createChild("div").textContent = title;
1088        pieChartContainer.appendChild(WebInspector.TimelineUIUtils.generatePieChart(aggregatedStats));
1089        this.showInDetails(WebInspector.UIString("Selected Range"), pieChartContainer);
1090    },
1091
1092    /**
1093     * @param {?WebInspector.TimelineSelection} selection
1094     */
1095    select: function(selection)
1096    {
1097        this._detailsLinkifier.reset();
1098        this._selection = selection;
1099
1100        for (var i = 0; i < this._currentViews.length; ++i) {
1101            var view = this._currentViews[i];
1102            view.setSelection(selection);
1103        }
1104        this._updateSelectionDetails();
1105    },
1106
1107    /**
1108     * @param {string} title
1109     * @param {!Node} node
1110     */
1111    showInDetails: function(title, node)
1112    {
1113        this._detailsView.setContent(title, node);
1114    },
1115
1116    __proto__: WebInspector.Panel.prototype
1117}
1118
1119/**
1120 * @constructor
1121 * @extends {WebInspector.TabbedPane}
1122 */
1123WebInspector.TimelineDetailsView = function()
1124{
1125    WebInspector.TabbedPane.call(this);
1126
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");
1133
1134    this.appendTab("default", WebInspector.UIString("Details"), this._defaultDetailsView);
1135
1136    this.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
1137}
1138
1139WebInspector.TimelineDetailsView.prototype = {
1140    /**
1141     * @param {string} title
1142     * @param {!Node} node
1143     */
1144    setContent: function(title, node)
1145    {
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);
1152    },
1153
1154    /**
1155     * @param {boolean} vertical
1156     */
1157    setVertical: function(vertical)
1158    {
1159        this._defaultDetailsContentElement.classList.toggle("hbox", !vertical);
1160        this._defaultDetailsContentElement.classList.toggle("vbox", vertical);
1161    },
1162
1163    /**
1164     * @override
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
1171     */
1172    appendTab: function(id, tabTitle, view, tabTooltip, userGesture, isCloseable)
1173    {
1174        WebInspector.TabbedPane.prototype.appendTab.call(this, id, tabTitle, view, tabTooltip);
1175        if (this._lastUserSelectedTabId !== this.selectedTabId)
1176            this.selectTab(id);
1177    },
1178
1179    _tabSelected: function(event)
1180    {
1181        if (!event.data.isUserGesture)
1182            return;
1183
1184        this._lastUserSelectedTabId = event.data.tabId;
1185    },
1186
1187    __proto__: WebInspector.TabbedPane.prototype
1188}
1189
1190/**
1191 * @constructor
1192 */
1193WebInspector.TimelineSelection = function()
1194{
1195}
1196
1197/**
1198 * @enum {string}
1199 */
1200WebInspector.TimelineSelection.Type = {
1201    Record: "Record",
1202    Frame: "Frame",
1203    TraceEvent: "TraceEvent",
1204};
1205
1206/**
1207 * @param {!WebInspector.TimelineModel.Record} record
1208 * @return {!WebInspector.TimelineSelection}
1209 */
1210WebInspector.TimelineSelection.fromRecord = function(record)
1211{
1212    var selection = new WebInspector.TimelineSelection();
1213    selection._type = WebInspector.TimelineSelection.Type.Record;
1214    selection._object = record;
1215    return selection;
1216}
1217
1218/**
1219 * @param {!WebInspector.TimelineFrame} frame
1220 * @return {!WebInspector.TimelineSelection}
1221 */
1222WebInspector.TimelineSelection.fromFrame = function(frame)
1223{
1224    var selection = new WebInspector.TimelineSelection();
1225    selection._type = WebInspector.TimelineSelection.Type.Frame;
1226    selection._object = frame;
1227    return selection;
1228}
1229
1230/**
1231 * @param {!WebInspector.TracingModel.Event} event
1232 * @return {!WebInspector.TimelineSelection}
1233 */
1234WebInspector.TimelineSelection.fromTraceEvent = function(event)
1235{
1236    var selection = new WebInspector.TimelineSelection();
1237    selection._type = WebInspector.TimelineSelection.Type.TraceEvent;
1238    selection._object = event;
1239    return selection;
1240}
1241
1242WebInspector.TimelineSelection.prototype = {
1243    /**
1244     * @return {!WebInspector.TimelineSelection.Type}
1245     */
1246    type: function()
1247    {
1248        return this._type;
1249    },
1250
1251    /**
1252     * @return {!Object}
1253     */
1254    object: function()
1255    {
1256        return this._object;
1257    }
1258};
1259
1260/**
1261 * @interface
1262 * @extends {WebInspector.EventTarget}
1263 */
1264WebInspector.TimelineModeView = function()
1265{
1266}
1267
1268WebInspector.TimelineModeView.prototype = {
1269    /**
1270     * @return {!WebInspector.View}
1271     */
1272    view: function() {},
1273
1274    dispose: function() {},
1275
1276    reset: function() {},
1277
1278    /**
1279     * @param {?RegExp} textFilter
1280     */
1281    refreshRecords: function(textFilter) {},
1282
1283    /**
1284     * @param {!WebInspector.TimelineModel.Record} record
1285     */
1286    addRecord: function(record) {},
1287
1288    /**
1289     * @param {?WebInspector.TimelineModel.Record} record
1290     * @param {string=} regex
1291     * @param {boolean=} selectRecord
1292     */
1293    highlightSearchResult: function(record, regex, selectRecord) {},
1294
1295    /**
1296     * @param {number} startTime
1297     * @param {number} endTime
1298     */
1299    setWindowTimes: function(startTime, endTime) {},
1300
1301    /**
1302     * @param {number} width
1303     */
1304    setSidebarSize: function(width) {},
1305
1306    /**
1307     * @param {?WebInspector.TimelineSelection} selection
1308     */
1309    setSelection: function(selection) {},
1310}
1311
1312/**
1313 * @interface
1314 */
1315WebInspector.TimelineModeViewDelegate = function() {}
1316
1317WebInspector.TimelineModeViewDelegate.prototype = {
1318    /**
1319     * @param {number} startTime
1320     * @param {number} endTime
1321     */
1322    requestWindowTimes: function(startTime, endTime) {},
1323
1324    /**
1325     * @param {?WebInspector.TimelineSelection} selection
1326     */
1327    select: function(selection) {},
1328
1329    /**
1330     * @param {string} title
1331     * @param {!Node} node
1332     */
1333    showInDetails: function(title, node) {},
1334}
1335
1336/**
1337 * @constructor
1338 * @extends {WebInspector.TimelineModel.Filter}
1339 * @param {!WebInspector.TimelineUIUtils} uiUtils
1340 */
1341WebInspector.TimelineCategoryFilter = function(uiUtils)
1342{
1343    WebInspector.TimelineModel.Filter.call(this);
1344    this._uiUtils = uiUtils;
1345}
1346
1347WebInspector.TimelineCategoryFilter.prototype = {
1348    /**
1349     * @param {!WebInspector.TimelineModel.Record} record
1350     * @return {boolean}
1351     */
1352    accept: function(record)
1353    {
1354        return !this._uiUtils.categoryForRecord(record).hidden;
1355    },
1356
1357    __proto__: WebInspector.TimelineModel.Filter.prototype
1358}
1359
1360/**
1361 * @constructor
1362 * @extends {WebInspector.TimelineModel.Filter}
1363 */
1364WebInspector.TimelineIsLongFilter = function()
1365{
1366    WebInspector.TimelineModel.Filter.call(this);
1367    this._minimumRecordDuration = 0;
1368}
1369
1370WebInspector.TimelineIsLongFilter.prototype = {
1371    /**
1372     * @param {number} value
1373     */
1374    setMinimumRecordDuration: function(value)
1375    {
1376        this._minimumRecordDuration = value;
1377        this.notifyFilterChanged();
1378    },
1379
1380    /**
1381     * @param {!WebInspector.TimelineModel.Record} record
1382     * @return {boolean}
1383     */
1384    accept: function(record)
1385    {
1386        return this._minimumRecordDuration ? ((record.endTime() - record.startTime()) >= this._minimumRecordDuration) : true;
1387    },
1388
1389    __proto__: WebInspector.TimelineModel.Filter.prototype
1390
1391}
1392
1393/**
1394 * @constructor
1395 * @extends {WebInspector.TimelineModel.Filter}
1396 * @param {!WebInspector.TimelineUIUtils} uiUtils
1397 */
1398WebInspector.TimelineTextFilter = function(uiUtils)
1399{
1400    WebInspector.TimelineModel.Filter.call(this);
1401    this._uiUtils = uiUtils;
1402}
1403
1404WebInspector.TimelineTextFilter.prototype = {
1405    /**
1406     * @return {boolean}
1407     */
1408    isEmpty: function()
1409    {
1410        return !this._regex;
1411    },
1412
1413    /**
1414     * @param {?RegExp} regex
1415     */
1416    setRegex: function(regex)
1417    {
1418        this._regex = regex;
1419        this.notifyFilterChanged();
1420    },
1421
1422    /**
1423     * @param {!WebInspector.TimelineModel.Record} record
1424     * @return {boolean}
1425     */
1426    accept: function(record)
1427    {
1428        return !this._regex || this._uiUtils.testContentMatching(record, this._regex);
1429    },
1430
1431    __proto__: WebInspector.TimelineModel.Filter.prototype
1432}
1433