b6ec0dde9fcc80a70be87fc7aa5572060f39d462
[WebKit-https.git] / Source / WebCore / inspector / front-end / TimelinePanel.js
1 /*
2  * Copyright (C) 2012 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 importScript("MemoryStatistics.js");
32 importScript("TimelineModel.js");
33 importScript("TimelineOverviewPane.js");
34 importScript("TimelinePresentationModel.js");
35 importScript("TimelineFrameController.js");
36
37 /**
38  * @constructor
39  * @extends {WebInspector.Panel}
40  */
41 WebInspector.TimelinePanel = function()
42 {
43     WebInspector.Panel.call(this, "timeline");
44     this.registerRequiredCSS("timelinePanel.css");
45
46     this._model = new WebInspector.TimelineModel();
47     this._presentationModel = new WebInspector.TimelinePresentationModel();
48
49     this._overviewPane = new WebInspector.TimelineOverviewPane(this._model);
50     this._overviewPane.addEventListener(WebInspector.TimelineOverviewPane.Events.WindowChanged, this._scheduleRefresh.bind(this, false));
51     this._overviewPane.addEventListener(WebInspector.TimelineOverviewPane.Events.ModeChanged, this._overviewModeChanged, this);
52     this._overviewPane.show(this.element);
53
54     this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false);
55     this.element.tabIndex = 0;
56
57     this._sidebarBackgroundElement = document.createElement("div");
58     this._sidebarBackgroundElement.className = "sidebar split-view-sidebar-left timeline-sidebar-background";
59     this.element.appendChild(this._sidebarBackgroundElement);
60
61     this.createSplitViewWithSidebarTree();
62     this.element.appendChild(this.splitView.sidebarResizerElement);
63
64     this._containerElement = this.splitView.element;
65     this._containerElement.id = "timeline-container";
66     this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false);
67
68     this._timelineMemorySplitter = this.element.createChild("div");
69     this._timelineMemorySplitter.id = "timeline-memory-splitter";
70     WebInspector.installDragHandle(this._timelineMemorySplitter, this._startSplitterDragging.bind(this), this._splitterDragging.bind(this), this._endSplitterDragging.bind(this), "ns-resize");
71     this._timelineMemorySplitter.addStyleClass("hidden");
72     this._memoryStatistics = new WebInspector.MemoryStatistics(this, this._model, this.splitView.preferredSidebarWidth());
73     WebInspector.settings.memoryCounterGraphsHeight = WebInspector.settings.createSetting("memoryCounterGraphsHeight", 150);
74
75     var itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true);
76     this.sidebarTree.appendChild(itemsTreeElement);
77
78     this._sidebarListElement = document.createElement("div");
79     this.sidebarElement.appendChild(this._sidebarListElement);
80
81     this._containerContentElement = this.splitView.mainElement;
82     this._containerContentElement.id = "resources-container-content";
83
84     this._timelineGrid = new WebInspector.TimelineGrid();
85     this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
86     this._itemsGraphsElement.id = "timeline-graphs";
87     this._containerContentElement.appendChild(this._timelineGrid.element);
88     this._timelineGrid.gridHeaderElement.id = "timeline-grid-header";
89     this._memoryStatistics.setMainTimelineGrid(this._timelineGrid);
90     this.element.appendChild(this._timelineGrid.gridHeaderElement);
91
92     this._topGapElement = document.createElement("div");
93     this._topGapElement.className = "timeline-gap";
94     this._itemsGraphsElement.appendChild(this._topGapElement);
95
96     this._graphRowsElement = document.createElement("div");
97     this._itemsGraphsElement.appendChild(this._graphRowsElement);
98
99     this._bottomGapElement = document.createElement("div");
100     this._bottomGapElement.className = "timeline-gap";
101     this._itemsGraphsElement.appendChild(this._bottomGapElement);
102
103     this._expandElements = document.createElement("div");
104     this._expandElements.id = "orphan-expand-elements";
105     this._itemsGraphsElement.appendChild(this._expandElements);
106
107     this._calculator = new WebInspector.TimelineCalculator(this._model);
108     var shortRecordThresholdTitle = Number.secondsToString(WebInspector.TimelinePresentationModel.shortRecordThreshold);
109     this._showShortRecordsTitleText = WebInspector.UIString("Show the records that are shorter than %s", shortRecordThresholdTitle);
110     this._hideShortRecordsTitleText = WebInspector.UIString("Hide the records that are shorter than %s", shortRecordThresholdTitle);
111     this._createStatusBarItems();
112
113     this._frameMode = false;
114     this._boundariesAreValid = true;
115     this._scrollTop = 0;
116
117     this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
118     this.element.addEventListener("mousemove", this._mouseMove.bind(this), false);
119     this.element.addEventListener("mouseout", this._mouseOut.bind(this), false);
120
121     // Disable short events filter by default.
122     this.toggleFilterButton.toggled = true;
123     this._showShortEvents = this.toggleFilterButton.toggled;
124     this._overviewPane.setShowShortEvents(this._showShortEvents);
125
126     this._timeStampRecords = [];
127     this._expandOffset = 15;
128
129     this._headerLineCount = 1;
130
131     this._mainThreadTasks = [];
132     this._mainThreadMonitoringEnabled = false;
133     if (WebInspector.experimentsSettings.mainThreadMonitoring.isEnabled())
134         this._enableMainThreadMonitoring();
135
136     this._createFileSelector();
137
138     this._model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onTimelineEventRecorded, this);
139     this._model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this);
140
141     this._registerShortcuts();
142
143     this._allRecordsCount = 0;
144
145     this._presentationModel.addFilter(this._overviewPane);
146     this._presentationModel.addFilter(new WebInspector.TimelineCategoryFilter()); 
147     this._presentationModel.addFilter(new WebInspector.TimelineIsLongFilter(this)); 
148
149     this._overviewModeSetting = WebInspector.settings.createSetting("timelineOverviewMode", WebInspector.TimelineOverviewPane.Mode.Events);
150 }
151
152 // Define row height, should be in sync with styles for timeline graphs.
153 WebInspector.TimelinePanel.rowHeight = 18;
154
155 WebInspector.TimelinePanel.prototype = {
156     /**
157      * @param {Event} event
158      * @return {boolean}
159      */
160     _startSplitterDragging: function(event)
161     {
162         this._dragOffset = this._timelineMemorySplitter.offsetTop + 2 - event.pageY;
163         return true;
164     },
165
166     /**
167      * @param {Event} event
168      */
169     _splitterDragging: function(event)
170     {
171         var top = event.pageY + this._dragOffset
172         this._setSplitterPosition(top);
173         event.preventDefault();
174     },
175
176     /**
177      * @param {Event} event
178      */
179     _endSplitterDragging: function(event)
180     {
181         delete this._dragOffset;
182         this._memoryStatistics.show();
183         WebInspector.settings.memoryCounterGraphsHeight.set(this.splitView.element.offsetHeight);
184     },
185
186     _setSplitterPosition: function(top)
187     {
188         const overviewHeight = 90;
189         const sectionMinHeight = 100;
190         top = Number.constrain(top, overviewHeight + sectionMinHeight, this.element.offsetHeight - sectionMinHeight);
191
192         this.splitView.element.style.height = (top - overviewHeight) + "px";
193         this._timelineMemorySplitter.style.top = (top - 2) + "px";
194         this._memoryStatistics.setTopPosition(top);
195         this._containerElementHeight = this._containerElement.clientHeight;
196         this.onResize();
197     },
198
199     get calculator()
200     {
201         return this._calculator;
202     },
203
204     get statusBarItems()
205     {
206         return this._statusBarButtons.select("element").concat([
207             this._miscStatusBarItems,
208             this.recordsCounter
209         ]);
210     },
211
212     defaultFocusedElement: function()
213     {
214         return this.element;
215     },
216
217     _createStatusBarItems: function()
218     {
219         this._statusBarButtons = [];
220
221         this.toggleFilterButton = new WebInspector.StatusBarButton(this._hideShortRecordsTitleText, "timeline-filter-status-bar-item");
222         this.toggleFilterButton.addEventListener("click", this._toggleFilterButtonClicked, this);
223         this._statusBarButtons.push(this.toggleFilterButton);
224
225         this.toggleTimelineButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record"), "record-profile-status-bar-item");
226         this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked, this);
227         this._statusBarButtons.push(this.toggleTimelineButton);
228
229         this.clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
230         this.clearButton.addEventListener("click", this._clearPanel, this);
231         this._statusBarButtons.push(this.clearButton);
232
233         this.garbageCollectButton = new WebInspector.StatusBarButton(WebInspector.UIString("Collect Garbage"), "garbage-collect-status-bar-item");
234         this.garbageCollectButton.addEventListener("click", this._garbageCollectButtonClicked, this);
235         this._statusBarButtons.push(this.garbageCollectButton);
236
237         this._glueParentButton = new WebInspector.StatusBarButton(WebInspector.UIString("Glue asynchronous events to causes"), "glue-async-status-bar-item");
238         this._glueParentButton.toggled = true;
239         this._presentationModel.setGlueRecords(true);
240         this._glueParentButton.addEventListener("click", this._glueParentButtonClicked, this);
241         this._statusBarButtons.push(this._glueParentButton);
242
243         this._miscStatusBarItems = document.createElement("div");
244         this._miscStatusBarItems.className = "status-bar-items";
245
246         this._statusBarFilters = this._miscStatusBarItems.createChild("div");
247         var categories = WebInspector.TimelinePresentationModel.categories();
248         for (var categoryName in categories) {
249             var category = categories[categoryName];
250             if (category.overviewStripGroupIndex < 0)
251                 continue;
252             this._statusBarFilters.appendChild(this._createTimelineCategoryStatusBarCheckbox(category, this._onCategoryCheckboxClicked.bind(this, category)));
253         }
254
255         this.recordsCounter = document.createElement("span");
256         this.recordsCounter.className = "timeline-records-counter";
257     },
258
259     _createTimelineCategoryStatusBarCheckbox: function(category, onCheckboxClicked)
260     {
261         var labelContainer = document.createElement("div");
262         labelContainer.addStyleClass("timeline-category-statusbar-item");
263         labelContainer.addStyleClass("timeline-category-" + category.name);
264         labelContainer.addStyleClass("status-bar-item");
265
266         var label = document.createElement("label");
267         var checkElement = document.createElement("input");
268         checkElement.type = "checkbox";
269         checkElement.className = "timeline-category-checkbox";
270         checkElement.checked = true;
271         checkElement.addEventListener("click", onCheckboxClicked, false);
272         label.appendChild(checkElement);
273
274         var typeElement = document.createElement("span");
275         typeElement.className = "type";
276         typeElement.textContent = category.title;
277         label.appendChild(typeElement);
278
279         labelContainer.appendChild(label);
280         return labelContainer;
281     },
282
283     _onCategoryCheckboxClicked: function(category, event)
284     {
285         category.hidden = !event.target.checked;
286         this._scheduleRefresh(true);
287     },
288
289     /**
290      * @param {?WebInspector.ProgressIndicator} indicator
291      */
292     _setOperationInProgress: function(indicator)
293     {
294         this._operationInProgress = !!indicator;
295         for (var i = 0; i < this._statusBarButtons.length; ++i)
296             this._statusBarButtons[i].disabled = this._operationInProgress;
297         this._glueParentButton.disabled = this._operationInProgress || !!this._frameController;
298         this._miscStatusBarItems.removeChildren();
299         this._miscStatusBarItems.appendChild(indicator ? indicator.element : this._statusBarFilters);
300     },
301
302     _registerShortcuts: function()
303     {
304         var shortcut = WebInspector.KeyboardShortcut;
305         var modifiers = shortcut.Modifiers;
306         var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("Timeline Panel"));
307
308         this._shortcuts[shortcut.makeKey("e", modifiers.CtrlOrMeta)] = this._toggleTimelineButtonClicked.bind(this);
309         section.addKey(shortcut.shortcutToString("e", modifiers.CtrlOrMeta), WebInspector.UIString("Start/stop recording"));
310
311         if (InspectorFrontendHost.canSave()) {
312             this._shortcuts[shortcut.makeKey("s", modifiers.CtrlOrMeta)] = this._saveToFile.bind(this);
313             section.addKey(shortcut.shortcutToString("s", modifiers.CtrlOrMeta), WebInspector.UIString("Save timeline data"));
314         }
315
316         this._shortcuts[shortcut.makeKey("o", modifiers.CtrlOrMeta)] = this._fileSelectorElement.click.bind(this._fileSelectorElement);
317         section.addKey(shortcut.shortcutToString("o", modifiers.CtrlOrMeta), WebInspector.UIString("Load timeline data"));
318     },
319
320     _createFileSelector: function()
321     {
322         if (this._fileSelectorElement)
323             this.element.removeChild(this._fileSelectorElement);
324
325         var fileSelectorElement = document.createElement("input");
326         fileSelectorElement.type = "file";
327         fileSelectorElement.style.zIndex = -1;
328         fileSelectorElement.style.position = "absolute";
329         fileSelectorElement.onchange = this._loadFromFile.bind(this);
330         this.element.appendChild(fileSelectorElement);
331         this._fileSelectorElement = fileSelectorElement;
332     },
333
334     _contextMenu: function(event)
335     {
336         var contextMenu = new WebInspector.ContextMenu();
337         if (InspectorFrontendHost.canSave())
338             contextMenu.appendItem(WebInspector.UIString("Save Timeline data\u2026"), this._saveToFile.bind(this), this._operationInProgress);
339         contextMenu.appendItem(WebInspector.UIString("Load Timeline data\u2026"), this._fileSelectorElement.click.bind(this._fileSelectorElement), this._operationInProgress);
340         contextMenu.show(event);
341     },
342
343     _saveToFile: function()
344     {
345         if (this._operationInProgress)
346             return;
347         this._model.saveToFile();
348     },
349
350     _loadFromFile: function()
351     {
352         if (this._operationInProgress)
353             return;
354         if (this.toggleTimelineButton.toggled) {
355             this.toggleTimelineButton.toggled = false;
356             this._model.stopRecord();
357         }
358         var progressIndicator = new WebInspector.ProgressIndicator();
359         progressIndicator.addEventListener(WebInspector.ProgressIndicator.Events.Done, this._setOperationInProgress.bind(this, null));
360         this._setOperationInProgress(progressIndicator);
361         this._model.loadFromFile(this._fileSelectorElement.files[0], progressIndicator);
362         this._createFileSelector();
363     },
364
365     _rootRecord: function()
366     {
367         return this._presentationModel.rootRecord();
368     },
369
370     _updateRecordsCounter: function(recordsInWindowCount)
371     {
372         this.recordsCounter.textContent = WebInspector.UIString("%d of %d captured records are visible", recordsInWindowCount, this._allRecordsCount);
373     },
374
375     _updateEventDividers: function()
376     {
377         this._timelineGrid.removeEventDividers();
378         var clientWidth = this._graphRowsElementWidth;
379         var dividers = [];
380
381         for (var i = 0; i < this._timeStampRecords.length; ++i) {
382             var record = this._timeStampRecords[i];
383             var positions = this._calculator.computeBarGraphWindowPosition(record);
384             var dividerPosition = Math.round(positions.left);
385             if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition])
386                 continue;
387             var divider = WebInspector.TimelinePresentationModel.createEventDivider(record.type, record.title);
388             divider.style.left = dividerPosition + "px";
389             dividers[dividerPosition] = divider;
390         }
391         this._timelineGrid.addEventDividers(dividers);
392     },
393
394     _shouldShowFrames: function()
395     {
396         return this._frameMode && this._presentationModel.frames().length > 0 && this.calculator.boundarySpan < 1.0;
397     },
398
399     _updateFrames: function()
400     {
401         var frames = this._presentationModel.frames();
402         var clientWidth = this._graphRowsElementWidth;
403         if (this._frameContainer)
404             this._frameContainer.removeChildren();
405         else {
406             const frameContainerBorderWidth = 1;
407             this._frameContainer = document.createElement("div");
408             this._frameContainer.addStyleClass("fill");
409             this._frameContainer.addStyleClass("timeline-frame-container");
410             this._frameContainer.style.height = this._headerLineCount * WebInspector.TimelinePanel.rowHeight + frameContainerBorderWidth + "px";
411             this._frameContainer.addEventListener("dblclick", this._onFrameDoubleClicked.bind(this), false);
412         }
413
414         var dividers = [ this._frameContainer ];
415
416         for (var i = 0; i < frames.length; ++i) {
417             var frame = frames[i];
418             var frameStart = this._calculator.computePosition(frame.startTime);
419             var frameEnd = this._calculator.computePosition(frame.endTime);
420             if (frameEnd <= 0 || frameStart >= clientWidth)
421                 continue;
422
423             var frameStrip = document.createElement("div");
424             frameStrip.className = "timeline-frame-strip";
425             var actualStart = Math.max(frameStart, 0);
426             var width = frameEnd - actualStart;
427             frameStrip.style.left = actualStart + "px";
428             frameStrip.style.width = width + "px";
429             frameStrip._frame = frame;
430
431             const minWidthForFrameInfo = 60;
432             if (width > minWidthForFrameInfo)
433                 frameStrip.textContent = Number.secondsToString(frame.endTime - frame.startTime, true);
434
435             this._frameContainer.appendChild(frameStrip);
436
437             if (actualStart > 0) {
438                 var frameMarker = WebInspector.TimelinePresentationModel.createEventDivider(WebInspector.TimelineModel.RecordType.BeginFrame);
439                 frameMarker.style.left = frameStart + "px";
440                 dividers.push(frameMarker);
441             }
442         }
443         this._timelineGrid.addEventDividers(dividers);
444     },
445
446     _onFrameDoubleClicked: function(event)
447     {
448         var frameBar = event.target.enclosingNodeOrSelfWithClass("timeline-frame-strip");
449         if (!frameBar)
450             return;
451         this._overviewPane.zoomToFrame(frameBar._frame);
452     },
453
454     _overviewModeChanged: function(event)
455     {
456         var mode = event.data;
457         var shouldShowMemory = mode === WebInspector.TimelineOverviewPane.Mode.Memory;
458         var frameMode = mode === WebInspector.TimelineOverviewPane.Mode.Frames;
459         this._overviewModeSetting.set(mode);
460         if (frameMode !== this._frameMode) {
461             this._frameMode = frameMode;
462             this._glueParentButton.disabled = frameMode;
463             this._presentationModel.setGlueRecords(this._glueParentButton.toggled && !frameMode);
464             this._repopulateRecords();
465
466             if (frameMode) {
467                 this.element.addStyleClass("timeline-frame-overview");
468                 this._frameController = new WebInspector.TimelineFrameController(this._model, this._overviewPane, this._presentationModel);
469             } else {
470                 this._frameController.dispose();
471                 this._frameController = null;
472                 this.element.removeStyleClass("timeline-frame-overview");
473             }
474         }
475         if (shouldShowMemory === this._memoryStatistics.visible())
476             return;
477         if (!shouldShowMemory) {
478             this._timelineMemorySplitter.addStyleClass("hidden");
479             this._memoryStatistics.hide();
480             this.splitView.element.style.height = "auto";
481             this.splitView.element.style.bottom = "0";
482             this.onResize();
483         } else {
484             this._timelineMemorySplitter.removeStyleClass("hidden");
485             this._memoryStatistics.show();
486             this.splitView.element.style.bottom = "auto";
487             this._setSplitterPosition(WebInspector.settings.memoryCounterGraphsHeight.get());
488         }
489     },
490
491     _toggleTimelineButtonClicked: function()
492     {
493         if (this._operationInProgress)
494             return;
495         if (this.toggleTimelineButton.toggled)
496             this._model.stopRecord();
497         else {
498             this._model.startRecord();
499             WebInspector.userMetrics.TimelineStarted.record();
500         }
501         this.toggleTimelineButton.toggled = !this.toggleTimelineButton.toggled;
502     },
503
504     _toggleFilterButtonClicked: function()
505     {
506         this.toggleFilterButton.toggled = !this.toggleFilterButton.toggled;
507         this._showShortEvents = this.toggleFilterButton.toggled;
508         this._overviewPane.setShowShortEvents(this._showShortEvents);
509         this.toggleFilterButton.element.title = this._showShortEvents ? this._hideShortRecordsTitleText : this._showShortRecordsTitleText;
510         this._scheduleRefresh(true);
511     },
512
513     _garbageCollectButtonClicked: function()
514     {
515         ProfilerAgent.collectGarbage();
516     },
517
518     _glueParentButtonClicked: function()
519     {
520         this._glueParentButton.toggled = !this._glueParentButton.toggled;
521         this._presentationModel.setGlueRecords(this._glueParentButton.toggled);
522         this._repopulateRecords();
523     },
524
525     _repopulateRecords: function()
526     {
527         this._resetPanel();
528         var records = this._model.records;
529         for (var i = 0; i < records.length; ++i)
530             this._innerAddRecordToTimeline(records[i], this._rootRecord());
531         this._scheduleRefresh(false);
532     },
533
534     _onTimelineEventRecorded: function(event)
535     {
536         if (this._innerAddRecordToTimeline(event.data, this._rootRecord()))
537             this._scheduleRefresh(false);
538     },
539
540     _innerAddRecordToTimeline: function(record, parentRecord)
541     {
542         if (record.type === WebInspector.TimelineModel.RecordType.Program) {
543             this._mainThreadTasks.push({
544                 startTime: WebInspector.TimelineModel.startTimeInSeconds(record),
545                 endTime: WebInspector.TimelineModel.endTimeInSeconds(record)
546             });
547         }
548
549         var records = this._presentationModel.addRecord(record, parentRecord);
550         this._allRecordsCount += records.length;
551         var timeStampRecords = this._timeStampRecords;
552         var hasVisibleRecords = false;
553         var presentationModel = this._presentationModel;
554         function processRecord(record)
555         {
556             if (WebInspector.TimelinePresentationModel.isEventDivider(record))
557                 timeStampRecords.push(record);
558             hasVisibleRecords |= presentationModel.isVisible(record);
559         }
560         WebInspector.TimelinePresentationModel.forAllRecords(records, processRecord);
561
562         function isAdoptedRecord(record)
563         {
564             return record.parent !== presentationModel.rootRecord;
565         }
566         // Tell caller update is necessary either if we added a visible record or if we re-parented a record.
567         return hasVisibleRecords || records.some(isAdoptedRecord);
568     },
569
570     sidebarResized: function(event)
571     {
572         var width = event.data;
573         this._sidebarBackgroundElement.style.width = width + "px";
574         this.onResize();
575         this._overviewPane.sidebarResized(width);
576         this._memoryStatistics.setSidebarWidth(width);
577         this._timelineGrid.gridHeaderElement.style.left = width + "px";
578     },
579
580     onResize: function()
581     {
582         this._closeRecordDetails();
583         this._scheduleRefresh(false);
584         this._graphRowsElementWidth = this._graphRowsElement.offsetWidth;
585         this._timelineGrid.gridHeaderElement.style.width = this._itemsGraphsElement.offsetWidth + "px";
586         this._containerElementHeight = this._containerElement.clientHeight;
587         var minFloatingStatusBarItemsOffset = document.getElementById("panel-status-bar").totalOffsetLeft() + this._statusBarButtons.length * WebInspector.StatusBarButton.width;
588         this._miscStatusBarItems.style.left = Math.max(minFloatingStatusBarItemsOffset, this.splitView.sidebarWidth()) + "px";
589     },
590
591     _clearPanel: function()
592     {
593         this._model.reset();
594     },
595
596     _onRecordsCleared: function()
597     {
598         this._resetPanel();
599         this._refresh();
600     },
601
602     _resetPanel: function()
603     {
604         this._presentationModel.reset();
605         this._timeStampRecords = [];
606         this._boundariesAreValid = false;
607         this._adjustScrollPosition(0);
608         this._closeRecordDetails();
609         this._allRecordsCount = 0;
610         this._automaticallySizeWindow = true;
611         this._mainThreadTasks = [];
612     },
613
614     elementsToRestoreScrollPositionsFor: function()
615     {
616         return [this._containerElement];
617     },
618
619     wasShown: function()
620     {
621         WebInspector.Panel.prototype.wasShown.call(this);
622         if (!WebInspector.TimelinePanel._categoryStylesInitialized) {
623             WebInspector.TimelinePanel._categoryStylesInitialized = true;
624             this._injectCategoryStyles();
625         }
626         this._overviewPane.setMode(this._overviewModeSetting.get());
627         this._refresh();
628     },
629
630     willHide: function()
631     {
632         this._closeRecordDetails();
633         WebInspector.Panel.prototype.willHide.call(this);
634     },
635
636     _onScroll: function(event)
637     {
638         this._closeRecordDetails();
639         this._scrollTop = this._containerElement.scrollTop;
640         var dividersTop = Math.max(0, this._scrollTop);
641         this._timelineGrid.setScrollAndDividerTop(this._scrollTop, dividersTop);
642         this._scheduleRefresh(true);
643     },
644
645     _scheduleRefresh: function(preserveBoundaries)
646     {
647         this._closeRecordDetails();
648         this._boundariesAreValid &= preserveBoundaries;
649
650         if (!this.isShowing())
651             return;
652
653         if (preserveBoundaries)
654             this._refresh();
655         else {
656             if (!this._refreshTimeout)
657                 this._refreshTimeout = setTimeout(this._refresh.bind(this), 300);
658         }
659     },
660
661     _refresh: function()
662     {
663         if (this._refreshTimeout) {
664             clearTimeout(this._refreshTimeout);
665             delete this._refreshTimeout;
666         }
667
668         this._timelinePaddingLeft = !this._overviewPane.windowLeft() ? this._expandOffset : 0;
669         this._calculator.setWindow(this._overviewPane.windowStartTime(), this._overviewPane.windowEndTime());
670         this._calculator.setDisplayWindow(this._timelinePaddingLeft, this._graphRowsElementWidth);
671
672         var recordsInWindowCount = this._refreshRecords();
673         this._updateRecordsCounter(recordsInWindowCount);
674         if(!this._boundariesAreValid) {
675             this._updateEventDividers();
676             if (this._shouldShowFrames()) {
677                 this._timelineGrid.removeDividers();
678                 this._updateFrames();
679             } else {
680                 this._timelineGrid.updateDividers(this._calculator);
681             }
682             if (this._mainThreadMonitoringEnabled)
683                 this._refreshMainThreadBars();
684         }
685         if (this._memoryStatistics.visible())
686             this._memoryStatistics.refresh();
687         this._boundariesAreValid = true;
688     },
689
690     revealRecordAt: function(time)
691     {
692         var recordsInWindow = this._presentationModel.filteredRecords();
693         var recordToReveal;
694         for (var i = 0; i < recordsInWindow.length; ++i) {
695             var record = recordsInWindow[i];
696             if (record.containsTime(time)) {
697                 recordToReveal = record;
698                 break;
699             }
700             // If there is no record containing the time than use the latest one before that time.
701             if (!recordToReveal || record.endTime < time && recordToReveal.endTime < record.endTime)
702                 recordToReveal = record;
703         }
704
705         // The record ends before the window left bound so scroll to the top.
706         if (!recordToReveal) {
707             this._containerElement.scrollTop = 0;
708             return;
709         }
710
711         // Expand all ancestors.
712         for (var parent = recordToReveal.parent; parent !== this._rootRecord(); parent = parent.parent)
713             parent.collapsed = false;
714         var index = recordsInWindow.indexOf(recordToReveal);
715         this._containerElement.scrollTop = index * WebInspector.TimelinePanel.rowHeight;
716     },
717
718     _refreshRecords: function()
719     {
720         var recordsInWindow = this._presentationModel.filteredRecords();
721
722         // Calculate the visible area.
723         var visibleTop = this._scrollTop;
724         var visibleBottom = visibleTop + this._containerElementHeight;
725
726         const rowHeight = WebInspector.TimelinePanel.rowHeight;
727
728         // Convert visible area to visible indexes. Always include top-level record for a visible nested record.
729         var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - this._headerLineCount, recordsInWindow.length - 1));
730         var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));
731         var lastVisibleLine = Math.max(0, Math.floor(visibleBottom / rowHeight) - this._headerLineCount);
732         if (this._automaticallySizeWindow && recordsInWindow.length > lastVisibleLine) {
733             this._automaticallySizeWindow = false;
734             // If we're at the top, always use real timeline start as a left window bound so that expansion arrow padding logic works.
735             var windowStartTime = startIndex ? recordsInWindow[startIndex].startTime : this._model.minimumRecordTime();
736             this._overviewPane.setWindowTimes(windowStartTime, recordsInWindow[Math.max(0, lastVisibleLine - 1)].endTime);
737             recordsInWindow = this._presentationModel.filteredRecords();
738             endIndex = Math.min(recordsInWindow.length, lastVisibleLine);
739         }
740
741         // Resize gaps first.
742         const top = (startIndex * rowHeight) + "px";
743         this._topGapElement.style.height = top;
744         this.sidebarElement.style.top = top;
745         this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px";
746
747         // Update visible rows.
748         var listRowElement = this._sidebarListElement.firstChild;
749         var width = this._graphRowsElementWidth;
750         this._itemsGraphsElement.removeChild(this._graphRowsElement);
751         var graphRowElement = this._graphRowsElement.firstChild;
752         var scheduleRefreshCallback = this._scheduleRefresh.bind(this, true);
753         this._itemsGraphsElement.removeChild(this._expandElements);
754         this._expandElements.removeChildren();
755
756         for (var i = 0; i < endIndex; ++i) {
757             var record = recordsInWindow[i];
758             var isEven = !(i % 2);
759
760             if (i < startIndex) {
761                 var lastChildIndex = i + record.visibleChildrenCount;
762                 if (lastChildIndex >= startIndex && lastChildIndex < endIndex) {
763                     var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements);
764                     var positions = this._calculator.computeBarGraphWindowPosition(record);
765                     expandElement._update(record, i, positions.left - this._expandOffset, positions.width);
766                 }
767             } else {
768                 if (!listRowElement) {
769                     listRowElement = new WebInspector.TimelineRecordListRow().element;
770                     this._sidebarListElement.appendChild(listRowElement);
771                 }
772                 if (!graphRowElement) {
773                     graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, scheduleRefreshCallback).element;
774                     this._graphRowsElement.appendChild(graphRowElement);
775                 }
776
777                 listRowElement.row.update(record, isEven, visibleTop);
778                 graphRowElement.row.update(record, isEven, this._calculator, this._expandOffset, i);
779
780                 listRowElement = listRowElement.nextSibling;
781                 graphRowElement = graphRowElement.nextSibling;
782             }
783         }
784
785         // Remove extra rows.
786         while (listRowElement) {
787             var nextElement = listRowElement.nextSibling;
788             listRowElement.row.dispose();
789             listRowElement = nextElement;
790         }
791         while (graphRowElement) {
792             var nextElement = graphRowElement.nextSibling;
793             graphRowElement.row.dispose();
794             graphRowElement = nextElement;
795         }
796
797         this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement);
798         this._itemsGraphsElement.appendChild(this._expandElements);
799         this._adjustScrollPosition((recordsInWindow.length + this._headerLineCount) * rowHeight);
800
801         return recordsInWindow.length;
802     },
803
804     _refreshMainThreadBars: function()
805     {
806         const barOffset = 3;
807         const minGap = 3;
808
809         var minWidth = WebInspector.TimelineCalculator._minWidth;
810         var widthAdjustment = minWidth / 2;
811
812         var width = this._graphRowsElementWidth;
813         var boundarySpan = this._overviewPane.windowEndTime() - this._overviewPane.windowStartTime();
814         var scale = boundarySpan / (width - minWidth - this._timelinePaddingLeft);
815         var startTime = this._overviewPane.windowStartTime() - this._timelinePaddingLeft * scale;
816         var endTime = startTime + width * scale;
817
818         var tasks = this._mainThreadTasks;
819
820         function compareEndTime(value, task)
821         {
822             return value < task.endTime ? -1 : 1;
823         }
824
825         var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, tasks, compareEndTime);
826
827         var container = this._cpuBarsElement;
828         var element = container.firstChild.nextSibling;
829         var lastElement;
830         var lastLeft;
831         var lastRight;
832
833         while (taskIndex < tasks.length) {
834             var task = tasks[taskIndex];
835             if (task.startTime > endTime)
836                 break;
837             taskIndex++;
838
839             var left = Math.max(0, this._calculator.computePosition(task.startTime) + barOffset - widthAdjustment);
840             var right = Math.min(width, this._calculator.computePosition(task.endTime) + barOffset + widthAdjustment);
841
842             if (lastElement) {
843                 var gap = Math.floor(left) - Math.ceil(lastRight);
844                 if (gap < minGap) {
845                     lastRight = right;
846                     continue;
847                 }
848                 lastElement.style.width = (lastRight - lastLeft) + "px";
849             }
850
851             if (!element)
852                 element = container.createChild("div", "timeline-graph-bar");
853
854             element.style.left = left + "px";
855             lastLeft = left;
856             lastRight = right;
857
858             lastElement = element;
859             element = element.nextSibling;
860         }
861
862         if (lastElement)
863             lastElement.style.width = (lastRight - lastLeft) + "px";
864
865         while (element) {
866             var nextElement = element.nextSibling;
867             container.removeChild(element);
868             element = nextElement;
869         }
870     },
871
872     _enableMainThreadMonitoring: function()
873     {
874         ++this._headerLineCount;
875
876         var container = this._timelineGrid.gridHeaderElement;
877         this._cpuBarsElement = container.createChild("div", "timeline-cpu-bars timeline-category-program");
878         var cpuBarsLabel = this._cpuBarsElement.createChild("span", "timeline-cpu-bars-label");
879         cpuBarsLabel.textContent = WebInspector.UIString("CPU");
880
881         const headerBorderWidth = 1;
882         const headerMargin = 2;
883
884         var headerHeight = this._headerLineCount * WebInspector.TimelinePanel.rowHeight;
885         this.sidebarElement.firstChild.style.height = headerHeight + "px";
886         this._timelineGrid.dividersLabelBarElement.style.height = headerHeight + headerMargin + "px";
887         this._itemsGraphsElement.style.top = headerHeight + headerBorderWidth + "px";
888
889         this._mainThreadMonitoringEnabled = true;
890     },
891
892     _adjustScrollPosition: function(totalHeight)
893     {
894         // Prevent the container from being scrolled off the end.
895         if ((this._scrollTop + this._containerElementHeight) > totalHeight + 1)
896             this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight);
897     },
898
899     _getPopoverAnchor: function(element)
900     {
901         return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") ||
902             element.enclosingNodeOrSelfWithClass("timeline-tree-item") ||
903             element.enclosingNodeOrSelfWithClass("timeline-frame-strip");
904     },
905
906     _mouseOut: function(e)
907     {
908         this._hideRectHighlight();
909     },
910
911     _mouseMove: function(e)
912     {
913         var anchor = this._getPopoverAnchor(e.target);
914
915         if (anchor && anchor.row && anchor.row._record.type === "Paint")
916             this._highlightRect(anchor.row._record);
917         else
918             this._hideRectHighlight();
919     },
920
921     _highlightRect: function(record)
922     {
923         if (this._highlightedRect === record.data)
924             return;
925         this._highlightedRect = record.data;
926         DOMAgent.highlightRect(this._highlightedRect.x, this._highlightedRect.y, this._highlightedRect.width, this._highlightedRect.height, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA());
927     },
928
929     _hideRectHighlight: function()
930     {
931         if (this._highlightedRect) {
932             delete this._highlightedRect;
933             DOMAgent.hideHighlight();
934         }
935     },
936
937     /**
938      * @param {Element} anchor
939      * @param {WebInspector.Popover} popover
940      */
941     _showPopover: function(anchor, popover)
942     {
943         if (anchor.hasStyleClass("timeline-frame-strip")) {
944             var frame = anchor._frame;
945             popover.show(WebInspector.TimelinePresentationModel.generatePopupContentForFrame(frame), anchor);
946         } else {
947             if (anchor.row && anchor.row._record)
948                 anchor.row._record.generatePopupContent(showCallback);
949         }
950
951         function showCallback(popupContent)
952         {
953             popover.show(popupContent, anchor);
954         }
955     },
956
957     _closeRecordDetails: function()
958     {
959         this._popoverHelper.hidePopover();
960     },
961
962     _injectCategoryStyles: function()
963     {
964         var style = document.createElement("style");
965         var categories = WebInspector.TimelinePresentationModel.categories();
966
967         style.textContent = Object.values(categories).map(WebInspector.TimelinePresentationModel.createStyleRuleForCategory).join("\n");
968         document.head.appendChild(style);
969     }
970 }
971
972 WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype;
973
974 /**
975  * @constructor
976  * @param {WebInspector.TimelineModel} model
977  */
978 WebInspector.TimelineCalculator = function(model)
979 {
980     this._model = model;
981 }
982
983 WebInspector.TimelineCalculator._minWidth = 5;
984
985 WebInspector.TimelineCalculator.prototype = {
986     /**
987      * @param {number} time
988      */
989     computePosition: function(time)
990     {
991         return (time - this.minimumBoundary) / this.boundarySpan * this._workingArea + this.paddingLeft;
992     },
993
994     computeBarGraphPercentages: function(record)
995     {
996         var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100;
997         var end = (record.startTime + record.selfTime - this.minimumBoundary) / this.boundarySpan * 100;
998         var endWithChildren = (record.lastChildEndTime - this.minimumBoundary) / this.boundarySpan * 100;
999         var cpuWidth = record.cpuTime / this.boundarySpan * 100;
1000         return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth};
1001     },
1002
1003     computeBarGraphWindowPosition: function(record)
1004     {
1005         var percentages = this.computeBarGraphPercentages(record);
1006         var widthAdjustment = 0;
1007
1008         var left = this.computePosition(record.startTime);
1009         var width = (percentages.end - percentages.start) / 100 * this._workingArea;
1010         if (width < WebInspector.TimelineCalculator._minWidth) {
1011             widthAdjustment = WebInspector.TimelineCalculator._minWidth - width;
1012             left -= widthAdjustment / 2;
1013             width += widthAdjustment;
1014         }
1015         var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * this._workingArea + widthAdjustment;
1016         var cpuWidth = percentages.cpuWidth / 100 * this._workingArea + widthAdjustment;
1017         if (percentages.endWithChildren > percentages.end)
1018             widthWithChildren += widthAdjustment;
1019         return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth};
1020     },
1021
1022     setWindow: function(minimumBoundary, maximumBoundary)
1023     {
1024         this.minimumBoundary = minimumBoundary;
1025         this.maximumBoundary = maximumBoundary;
1026         this.boundarySpan = this.maximumBoundary - this.minimumBoundary;
1027     },
1028
1029     /**
1030      * @param {number} paddingLeft
1031      * @param {number} clientWidth
1032      */
1033     setDisplayWindow: function(paddingLeft, clientWidth)
1034     {
1035         this._workingArea = clientWidth - WebInspector.TimelineCalculator._minWidth - paddingLeft;
1036         this.paddingLeft = paddingLeft;
1037     },
1038
1039     formatTime: function(value)
1040     {
1041         return Number.secondsToString(value + this.minimumBoundary - this._model.minimumRecordTime());
1042     }
1043 }
1044
1045 /**
1046  * @constructor
1047  */
1048 WebInspector.TimelineRecordListRow = function()
1049 {
1050     this.element = document.createElement("div");
1051     this.element.row = this;
1052     this.element.style.cursor = "pointer";
1053     var iconElement = document.createElement("span");
1054     iconElement.className = "timeline-tree-icon";
1055     this.element.appendChild(iconElement);
1056
1057     this._typeElement = document.createElement("span");
1058     this._typeElement.className = "type";
1059     this.element.appendChild(this._typeElement);
1060
1061     var separatorElement = document.createElement("span");
1062     separatorElement.className = "separator";
1063     separatorElement.textContent = " ";
1064
1065     this._dataElement = document.createElement("span");
1066     this._dataElement.className = "data dimmed";
1067
1068     this.element.appendChild(separatorElement);
1069     this.element.appendChild(this._dataElement);
1070 }
1071
1072 WebInspector.TimelineRecordListRow.prototype = {
1073     update: function(record, isEven, offset)
1074     {
1075         this._record = record;
1076         this._offset = offset;
1077
1078         this.element.className = "timeline-tree-item timeline-category-" + record.category.name + (isEven ? " even" : "");
1079         this._typeElement.textContent = record.title;
1080
1081         if (this._dataElement.firstChild)
1082             this._dataElement.removeChildren();
1083         var details = record.details();
1084         if (details) {
1085             var detailsContainer = document.createElement("span");
1086             if (typeof details === "object") {
1087                 detailsContainer.appendChild(document.createTextNode("("));
1088                 detailsContainer.appendChild(details);
1089                 detailsContainer.appendChild(document.createTextNode(")"));
1090             } else
1091                 detailsContainer.textContent = "(" + details + ")";
1092             this._dataElement.appendChild(detailsContainer);
1093         }
1094     },
1095
1096     dispose: function()
1097     {
1098         this.element.parentElement.removeChild(this.element);
1099     }
1100 }
1101
1102 /**
1103  * @constructor
1104  */
1105 WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh)
1106 {
1107     this.element = document.createElement("div");
1108     this.element.row = this;
1109
1110     this._barAreaElement = document.createElement("div");
1111     this._barAreaElement.className = "timeline-graph-bar-area";
1112     this.element.appendChild(this._barAreaElement);
1113
1114     this._barWithChildrenElement = document.createElement("div");
1115     this._barWithChildrenElement.className = "timeline-graph-bar with-children";
1116     this._barWithChildrenElement.row = this;
1117     this._barAreaElement.appendChild(this._barWithChildrenElement);
1118
1119     this._barCpuElement = document.createElement("div");
1120     this._barCpuElement.className = "timeline-graph-bar cpu"
1121     this._barCpuElement.row = this;
1122     this._barAreaElement.appendChild(this._barCpuElement);
1123
1124     this._barElement = document.createElement("div");
1125     this._barElement.className = "timeline-graph-bar";
1126     this._barElement.row = this;
1127     this._barAreaElement.appendChild(this._barElement);
1128
1129     this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer);
1130     this._expandElement._element.addEventListener("click", this._onClick.bind(this));
1131
1132     this._scheduleRefresh = scheduleRefresh;
1133 }
1134
1135 WebInspector.TimelineRecordGraphRow.prototype = {
1136     update: function(record, isEven, calculator, expandOffset, index)
1137     {
1138         this._record = record;
1139         this.element.className = "timeline-graph-side timeline-category-" + record.category.name + (isEven ? " even" : "");
1140         var barPosition = calculator.computeBarGraphWindowPosition(record);
1141         this._barWithChildrenElement.style.left = barPosition.left + "px";
1142         this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px";
1143         this._barElement.style.left = barPosition.left + "px";
1144         this._barElement.style.width = barPosition.width + "px";
1145         this._barCpuElement.style.left = barPosition.left + "px";
1146         this._barCpuElement.style.width = barPosition.cpuWidth + "px";
1147         this._expandElement._update(record, index, barPosition.left - expandOffset, barPosition.width);
1148     },
1149
1150     _onClick: function(event)
1151     {
1152         this._record.collapsed = !this._record.collapsed;
1153         this._scheduleRefresh(false);
1154     },
1155
1156     dispose: function()
1157     {
1158         this.element.parentElement.removeChild(this.element);
1159         this._expandElement._dispose();
1160     }
1161 }
1162
1163 /**
1164  * @constructor
1165  */
1166 WebInspector.TimelineExpandableElement = function(container)
1167 {
1168     this._element = document.createElement("div");
1169     this._element.className = "timeline-expandable";
1170
1171     var leftBorder = document.createElement("div");
1172     leftBorder.className = "timeline-expandable-left";
1173     this._element.appendChild(leftBorder);
1174
1175     container.appendChild(this._element);
1176 }
1177
1178 WebInspector.TimelineExpandableElement.prototype = {
1179     _update: function(record, index, left, width)
1180     {
1181         const rowHeight = WebInspector.TimelinePanel.rowHeight;
1182         if (record.visibleChildrenCount || record.invisibleChildrenCount) {
1183             this._element.style.top = index * rowHeight + "px";
1184             this._element.style.left = left + "px";
1185             this._element.style.width = Math.max(12, width + 25) + "px";
1186             if (!record.collapsed) {
1187                 this._element.style.height = (record.visibleChildrenCount + 1) * rowHeight + "px";
1188                 this._element.addStyleClass("timeline-expandable-expanded");
1189                 this._element.removeStyleClass("timeline-expandable-collapsed");
1190             } else {
1191                 this._element.style.height = rowHeight + "px";
1192                 this._element.addStyleClass("timeline-expandable-collapsed");
1193                 this._element.removeStyleClass("timeline-expandable-expanded");
1194             }
1195             this._element.removeStyleClass("hidden");
1196         } else
1197             this._element.addStyleClass("hidden");
1198     },
1199
1200     _dispose: function()
1201     {
1202         this._element.parentElement.removeChild(this._element);
1203     }
1204 }
1205
1206 /**
1207  * @constructor
1208  * @implements {WebInspector.TimelinePresentationModel.Filter}
1209  */
1210 WebInspector.TimelineCategoryFilter = function()
1211 {
1212 }
1213
1214 WebInspector.TimelineCategoryFilter.prototype = {
1215     /**
1216      * @param {WebInspector.TimelinePresentationModel.Record} record
1217      */
1218     accept: function(record)
1219     {
1220         return !record.category.hidden && record.type !== WebInspector.TimelineModel.RecordType.BeginFrame;
1221     }
1222 }
1223
1224 /**
1225  * @param {WebInspector.TimelinePanel} panel
1226  * @constructor
1227  * @implements {WebInspector.TimelinePresentationModel.Filter}
1228  */
1229 WebInspector.TimelineIsLongFilter = function(panel)
1230 {
1231     this._panel = panel;
1232 }
1233
1234 WebInspector.TimelineIsLongFilter.prototype = {
1235     /**
1236      * @param {WebInspector.TimelinePresentationModel.Record} record
1237      */
1238     accept: function(record)
1239     {
1240         return this._panel._showShortEvents || record.isLong();
1241     }
1242 }