Web Inspector: Embeddable Web Inspector
[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         var progressIndicator = this._prepareToLoadTimeline();
353         if (!progressIndicator)
354             return;
355         this._model.loadFromFile(this._fileSelectorElement.files[0], progressIndicator);
356         this._createFileSelector();
357     },
358
359     /**
360      * @param {string} url
361      */
362     loadFromURL: function(url)
363     {
364         var progressIndicator = this._prepareToLoadTimeline();
365         if (!progressIndicator)
366             return;
367         this._model.loadFromURL(url, progressIndicator);
368     },
369
370     /**
371      * @return {?WebInspector.ProgressIndicator}
372      */
373     _prepareToLoadTimeline: function()
374     {
375         if (this._operationInProgress)
376             return null;
377         if (this.toggleTimelineButton.toggled) {
378             this.toggleTimelineButton.toggled = false;
379             this._model.stopRecord();
380         }
381         var progressIndicator = new WebInspector.ProgressIndicator();
382         progressIndicator.addEventListener(WebInspector.ProgressIndicator.Events.Done, this._setOperationInProgress.bind(this, null));
383         this._setOperationInProgress(progressIndicator);
384         return progressIndicator;
385     },
386
387     _rootRecord: function()
388     {
389         return this._presentationModel.rootRecord();
390     },
391
392     _updateRecordsCounter: function(recordsInWindowCount)
393     {
394         this.recordsCounter.textContent = WebInspector.UIString("%d of %d captured records are visible", recordsInWindowCount, this._allRecordsCount);
395     },
396
397     _updateEventDividers: function()
398     {
399         this._timelineGrid.removeEventDividers();
400         var clientWidth = this._graphRowsElementWidth;
401         var dividers = [];
402
403         for (var i = 0; i < this._timeStampRecords.length; ++i) {
404             var record = this._timeStampRecords[i];
405             var positions = this._calculator.computeBarGraphWindowPosition(record);
406             var dividerPosition = Math.round(positions.left);
407             if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition])
408                 continue;
409             var divider = WebInspector.TimelinePresentationModel.createEventDivider(record.type, record.title);
410             divider.style.left = dividerPosition + "px";
411             dividers[dividerPosition] = divider;
412         }
413         this._timelineGrid.addEventDividers(dividers);
414     },
415
416     _shouldShowFrames: function()
417     {
418         return this._frameMode && this._presentationModel.frames().length > 0 && this.calculator.boundarySpan < 1.0;
419     },
420
421     _updateFrames: function()
422     {
423         var frames = this._presentationModel.frames();
424         var clientWidth = this._graphRowsElementWidth;
425         if (this._frameContainer)
426             this._frameContainer.removeChildren();
427         else {
428             const frameContainerBorderWidth = 1;
429             this._frameContainer = document.createElement("div");
430             this._frameContainer.addStyleClass("fill");
431             this._frameContainer.addStyleClass("timeline-frame-container");
432             this._frameContainer.style.height = this._headerLineCount * WebInspector.TimelinePanel.rowHeight + frameContainerBorderWidth + "px";
433             this._frameContainer.addEventListener("dblclick", this._onFrameDoubleClicked.bind(this), false);
434         }
435
436         var dividers = [ this._frameContainer ];
437
438         for (var i = 0; i < frames.length; ++i) {
439             var frame = frames[i];
440             var frameStart = this._calculator.computePosition(frame.startTime);
441             var frameEnd = this._calculator.computePosition(frame.endTime);
442             if (frameEnd <= 0 || frameStart >= clientWidth)
443                 continue;
444
445             var frameStrip = document.createElement("div");
446             frameStrip.className = "timeline-frame-strip";
447             var actualStart = Math.max(frameStart, 0);
448             var width = frameEnd - actualStart;
449             frameStrip.style.left = actualStart + "px";
450             frameStrip.style.width = width + "px";
451             frameStrip._frame = frame;
452
453             const minWidthForFrameInfo = 60;
454             if (width > minWidthForFrameInfo)
455                 frameStrip.textContent = Number.secondsToString(frame.endTime - frame.startTime, true);
456
457             this._frameContainer.appendChild(frameStrip);
458
459             if (actualStart > 0) {
460                 var frameMarker = WebInspector.TimelinePresentationModel.createEventDivider(WebInspector.TimelineModel.RecordType.BeginFrame);
461                 frameMarker.style.left = frameStart + "px";
462                 dividers.push(frameMarker);
463             }
464         }
465         this._timelineGrid.addEventDividers(dividers);
466     },
467
468     _onFrameDoubleClicked: function(event)
469     {
470         var frameBar = event.target.enclosingNodeOrSelfWithClass("timeline-frame-strip");
471         if (!frameBar)
472             return;
473         this._overviewPane.zoomToFrame(frameBar._frame);
474     },
475
476     _overviewModeChanged: function(event)
477     {
478         var mode = event.data;
479         var shouldShowMemory = mode === WebInspector.TimelineOverviewPane.Mode.Memory;
480         var frameMode = mode === WebInspector.TimelineOverviewPane.Mode.Frames;
481         this._overviewModeSetting.set(mode);
482         if (frameMode !== this._frameMode) {
483             this._frameMode = frameMode;
484             this._glueParentButton.disabled = frameMode;
485             this._presentationModel.setGlueRecords(this._glueParentButton.toggled && !frameMode);
486             this._repopulateRecords();
487
488             if (frameMode) {
489                 this.element.addStyleClass("timeline-frame-overview");
490                 this._frameController = new WebInspector.TimelineFrameController(this._model, this._overviewPane, this._presentationModel);
491             } else {
492                 this._frameController.dispose();
493                 this._frameController = null;
494                 this.element.removeStyleClass("timeline-frame-overview");
495             }
496         }
497         if (shouldShowMemory === this._memoryStatistics.visible())
498             return;
499         if (!shouldShowMemory) {
500             this._timelineMemorySplitter.addStyleClass("hidden");
501             this._memoryStatistics.hide();
502             this.splitView.element.style.height = "auto";
503             this.splitView.element.style.bottom = "0";
504             this.onResize();
505         } else {
506             this._timelineMemorySplitter.removeStyleClass("hidden");
507             this._memoryStatistics.show();
508             this.splitView.element.style.bottom = "auto";
509             this._setSplitterPosition(WebInspector.settings.memoryCounterGraphsHeight.get());
510         }
511     },
512
513     _toggleTimelineButtonClicked: function()
514     {
515         if (this._operationInProgress)
516             return;
517         if (this.toggleTimelineButton.toggled)
518             this._model.stopRecord();
519         else {
520             this._model.startRecord();
521             WebInspector.userMetrics.TimelineStarted.record();
522         }
523         this.toggleTimelineButton.toggled = !this.toggleTimelineButton.toggled;
524     },
525
526     _toggleFilterButtonClicked: function()
527     {
528         this.toggleFilterButton.toggled = !this.toggleFilterButton.toggled;
529         this._showShortEvents = this.toggleFilterButton.toggled;
530         this._overviewPane.setShowShortEvents(this._showShortEvents);
531         this.toggleFilterButton.element.title = this._showShortEvents ? this._hideShortRecordsTitleText : this._showShortRecordsTitleText;
532         this._scheduleRefresh(true);
533     },
534
535     _garbageCollectButtonClicked: function()
536     {
537         ProfilerAgent.collectGarbage();
538     },
539
540     _glueParentButtonClicked: function()
541     {
542         this._glueParentButton.toggled = !this._glueParentButton.toggled;
543         this._presentationModel.setGlueRecords(this._glueParentButton.toggled);
544         this._repopulateRecords();
545     },
546
547     _repopulateRecords: function()
548     {
549         this._resetPanel();
550         var records = this._model.records;
551         for (var i = 0; i < records.length; ++i)
552             this._innerAddRecordToTimeline(records[i], this._rootRecord());
553         this._scheduleRefresh(false);
554     },
555
556     _onTimelineEventRecorded: function(event)
557     {
558         if (this._innerAddRecordToTimeline(event.data, this._rootRecord()))
559             this._scheduleRefresh(false);
560     },
561
562     _innerAddRecordToTimeline: function(record, parentRecord)
563     {
564         if (record.type === WebInspector.TimelineModel.RecordType.Program) {
565             this._mainThreadTasks.push({
566                 startTime: WebInspector.TimelineModel.startTimeInSeconds(record),
567                 endTime: WebInspector.TimelineModel.endTimeInSeconds(record)
568             });
569         }
570
571         var records = this._presentationModel.addRecord(record, parentRecord);
572         this._allRecordsCount += records.length;
573         var timeStampRecords = this._timeStampRecords;
574         var hasVisibleRecords = false;
575         var presentationModel = this._presentationModel;
576         function processRecord(record)
577         {
578             if (WebInspector.TimelinePresentationModel.isEventDivider(record))
579                 timeStampRecords.push(record);
580             hasVisibleRecords |= presentationModel.isVisible(record);
581         }
582         WebInspector.TimelinePresentationModel.forAllRecords(records, processRecord);
583
584         function isAdoptedRecord(record)
585         {
586             return record.parent !== presentationModel.rootRecord;
587         }
588         // Tell caller update is necessary either if we added a visible record or if we re-parented a record.
589         return hasVisibleRecords || records.some(isAdoptedRecord);
590     },
591
592     sidebarResized: function(event)
593     {
594         var width = event.data;
595         this._sidebarBackgroundElement.style.width = width + "px";
596         this.onResize();
597         this._overviewPane.sidebarResized(width);
598         this._memoryStatistics.setSidebarWidth(width);
599         this._timelineGrid.gridHeaderElement.style.left = width + "px";
600     },
601
602     onResize: function()
603     {
604         this._closeRecordDetails();
605         this._scheduleRefresh(false);
606         this._graphRowsElementWidth = this._graphRowsElement.offsetWidth;
607         this._timelineGrid.gridHeaderElement.style.width = this._itemsGraphsElement.offsetWidth + "px";
608         this._containerElementHeight = this._containerElement.clientHeight;
609         var minFloatingStatusBarItemsOffset = document.getElementById("panel-status-bar").totalOffsetLeft() + this._statusBarButtons.length * WebInspector.StatusBarButton.width;
610         this._miscStatusBarItems.style.left = Math.max(minFloatingStatusBarItemsOffset, this.splitView.sidebarWidth()) + "px";
611     },
612
613     _clearPanel: function()
614     {
615         this._model.reset();
616     },
617
618     _onRecordsCleared: function()
619     {
620         this._resetPanel();
621         this._refresh();
622     },
623
624     _resetPanel: function()
625     {
626         this._presentationModel.reset();
627         this._timeStampRecords = [];
628         this._boundariesAreValid = false;
629         this._adjustScrollPosition(0);
630         this._closeRecordDetails();
631         this._allRecordsCount = 0;
632         this._automaticallySizeWindow = true;
633         this._mainThreadTasks = [];
634     },
635
636     elementsToRestoreScrollPositionsFor: function()
637     {
638         return [this._containerElement];
639     },
640
641     wasShown: function()
642     {
643         WebInspector.Panel.prototype.wasShown.call(this);
644         if (!WebInspector.TimelinePanel._categoryStylesInitialized) {
645             WebInspector.TimelinePanel._categoryStylesInitialized = true;
646             this._injectCategoryStyles();
647         }
648         this._overviewPane.setMode(this._overviewModeSetting.get());
649         this._refresh();
650     },
651
652     willHide: function()
653     {
654         this._closeRecordDetails();
655         WebInspector.Panel.prototype.willHide.call(this);
656     },
657
658     _onScroll: function(event)
659     {
660         this._closeRecordDetails();
661         this._scrollTop = this._containerElement.scrollTop;
662         var dividersTop = Math.max(0, this._scrollTop);
663         this._timelineGrid.setScrollAndDividerTop(this._scrollTop, dividersTop);
664         this._scheduleRefresh(true);
665     },
666
667     _scheduleRefresh: function(preserveBoundaries)
668     {
669         this._closeRecordDetails();
670         this._boundariesAreValid &= preserveBoundaries;
671
672         if (!this.isShowing())
673             return;
674
675         if (preserveBoundaries)
676             this._refresh();
677         else {
678             if (!this._refreshTimeout)
679                 this._refreshTimeout = setTimeout(this._refresh.bind(this), 300);
680         }
681     },
682
683     _refresh: function()
684     {
685         if (this._refreshTimeout) {
686             clearTimeout(this._refreshTimeout);
687             delete this._refreshTimeout;
688         }
689
690         this._timelinePaddingLeft = !this._overviewPane.windowLeft() ? this._expandOffset : 0;
691         this._calculator.setWindow(this._overviewPane.windowStartTime(), this._overviewPane.windowEndTime());
692         this._calculator.setDisplayWindow(this._timelinePaddingLeft, this._graphRowsElementWidth);
693
694         var recordsInWindowCount = this._refreshRecords();
695         this._updateRecordsCounter(recordsInWindowCount);
696         if(!this._boundariesAreValid) {
697             this._updateEventDividers();
698             if (this._shouldShowFrames()) {
699                 this._timelineGrid.removeDividers();
700                 this._updateFrames();
701             } else {
702                 this._timelineGrid.updateDividers(this._calculator);
703             }
704             if (this._mainThreadMonitoringEnabled)
705                 this._refreshMainThreadBars();
706         }
707         if (this._memoryStatistics.visible())
708             this._memoryStatistics.refresh();
709         this._boundariesAreValid = true;
710     },
711
712     revealRecordAt: function(time)
713     {
714         var recordsInWindow = this._presentationModel.filteredRecords();
715         var recordToReveal;
716         for (var i = 0; i < recordsInWindow.length; ++i) {
717             var record = recordsInWindow[i];
718             if (record.containsTime(time)) {
719                 recordToReveal = record;
720                 break;
721             }
722             // If there is no record containing the time than use the latest one before that time.
723             if (!recordToReveal || record.endTime < time && recordToReveal.endTime < record.endTime)
724                 recordToReveal = record;
725         }
726
727         // The record ends before the window left bound so scroll to the top.
728         if (!recordToReveal) {
729             this._containerElement.scrollTop = 0;
730             return;
731         }
732
733         // Expand all ancestors.
734         for (var parent = recordToReveal.parent; parent !== this._rootRecord(); parent = parent.parent)
735             parent.collapsed = false;
736         var index = recordsInWindow.indexOf(recordToReveal);
737         this._containerElement.scrollTop = index * WebInspector.TimelinePanel.rowHeight;
738     },
739
740     _refreshRecords: function()
741     {
742         var recordsInWindow = this._presentationModel.filteredRecords();
743
744         // Calculate the visible area.
745         var visibleTop = this._scrollTop;
746         var visibleBottom = visibleTop + this._containerElementHeight;
747
748         const rowHeight = WebInspector.TimelinePanel.rowHeight;
749
750         // Convert visible area to visible indexes. Always include top-level record for a visible nested record.
751         var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - this._headerLineCount, recordsInWindow.length - 1));
752         var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));
753         var lastVisibleLine = Math.max(0, Math.floor(visibleBottom / rowHeight) - this._headerLineCount);
754         if (this._automaticallySizeWindow && recordsInWindow.length > lastVisibleLine) {
755             this._automaticallySizeWindow = false;
756             // If we're at the top, always use real timeline start as a left window bound so that expansion arrow padding logic works.
757             var windowStartTime = startIndex ? recordsInWindow[startIndex].startTime : this._model.minimumRecordTime();
758             this._overviewPane.setWindowTimes(windowStartTime, recordsInWindow[Math.max(0, lastVisibleLine - 1)].endTime);
759             recordsInWindow = this._presentationModel.filteredRecords();
760             endIndex = Math.min(recordsInWindow.length, lastVisibleLine);
761         }
762
763         // Resize gaps first.
764         const top = (startIndex * rowHeight) + "px";
765         this._topGapElement.style.height = top;
766         this.sidebarElement.style.top = top;
767         this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px";
768
769         // Update visible rows.
770         var listRowElement = this._sidebarListElement.firstChild;
771         var width = this._graphRowsElementWidth;
772         this._itemsGraphsElement.removeChild(this._graphRowsElement);
773         var graphRowElement = this._graphRowsElement.firstChild;
774         var scheduleRefreshCallback = this._scheduleRefresh.bind(this, true);
775         this._itemsGraphsElement.removeChild(this._expandElements);
776         this._expandElements.removeChildren();
777
778         for (var i = 0; i < endIndex; ++i) {
779             var record = recordsInWindow[i];
780             var isEven = !(i % 2);
781
782             if (i < startIndex) {
783                 var lastChildIndex = i + record.visibleChildrenCount;
784                 if (lastChildIndex >= startIndex && lastChildIndex < endIndex) {
785                     var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements);
786                     var positions = this._calculator.computeBarGraphWindowPosition(record);
787                     expandElement._update(record, i, positions.left - this._expandOffset, positions.width);
788                 }
789             } else {
790                 if (!listRowElement) {
791                     listRowElement = new WebInspector.TimelineRecordListRow().element;
792                     this._sidebarListElement.appendChild(listRowElement);
793                 }
794                 if (!graphRowElement) {
795                     graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, scheduleRefreshCallback).element;
796                     this._graphRowsElement.appendChild(graphRowElement);
797                 }
798
799                 listRowElement.row.update(record, isEven, visibleTop);
800                 graphRowElement.row.update(record, isEven, this._calculator, this._expandOffset, i);
801
802                 listRowElement = listRowElement.nextSibling;
803                 graphRowElement = graphRowElement.nextSibling;
804             }
805         }
806
807         // Remove extra rows.
808         while (listRowElement) {
809             var nextElement = listRowElement.nextSibling;
810             listRowElement.row.dispose();
811             listRowElement = nextElement;
812         }
813         while (graphRowElement) {
814             var nextElement = graphRowElement.nextSibling;
815             graphRowElement.row.dispose();
816             graphRowElement = nextElement;
817         }
818
819         this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement);
820         this._itemsGraphsElement.appendChild(this._expandElements);
821         this._adjustScrollPosition((recordsInWindow.length + this._headerLineCount) * rowHeight);
822
823         return recordsInWindow.length;
824     },
825
826     _refreshMainThreadBars: function()
827     {
828         const barOffset = 3;
829         const minGap = 3;
830
831         var minWidth = WebInspector.TimelineCalculator._minWidth;
832         var widthAdjustment = minWidth / 2;
833
834         var width = this._graphRowsElementWidth;
835         var boundarySpan = this._overviewPane.windowEndTime() - this._overviewPane.windowStartTime();
836         var scale = boundarySpan / (width - minWidth - this._timelinePaddingLeft);
837         var startTime = this._overviewPane.windowStartTime() - this._timelinePaddingLeft * scale;
838         var endTime = startTime + width * scale;
839
840         var tasks = this._mainThreadTasks;
841
842         function compareEndTime(value, task)
843         {
844             return value < task.endTime ? -1 : 1;
845         }
846
847         var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, tasks, compareEndTime);
848
849         var container = this._cpuBarsElement;
850         var element = container.firstChild.nextSibling;
851         var lastElement;
852         var lastLeft;
853         var lastRight;
854
855         while (taskIndex < tasks.length) {
856             var task = tasks[taskIndex];
857             if (task.startTime > endTime)
858                 break;
859             taskIndex++;
860
861             var left = Math.max(0, this._calculator.computePosition(task.startTime) + barOffset - widthAdjustment);
862             var right = Math.min(width, this._calculator.computePosition(task.endTime) + barOffset + widthAdjustment);
863
864             if (lastElement) {
865                 var gap = Math.floor(left) - Math.ceil(lastRight);
866                 if (gap < minGap) {
867                     lastRight = right;
868                     continue;
869                 }
870                 lastElement.style.width = (lastRight - lastLeft) + "px";
871             }
872
873             if (!element)
874                 element = container.createChild("div", "timeline-graph-bar");
875
876             element.style.left = left + "px";
877             lastLeft = left;
878             lastRight = right;
879
880             lastElement = element;
881             element = element.nextSibling;
882         }
883
884         if (lastElement)
885             lastElement.style.width = (lastRight - lastLeft) + "px";
886
887         while (element) {
888             var nextElement = element.nextSibling;
889             container.removeChild(element);
890             element = nextElement;
891         }
892     },
893
894     _enableMainThreadMonitoring: function()
895     {
896         ++this._headerLineCount;
897
898         var container = this._timelineGrid.gridHeaderElement;
899         this._cpuBarsElement = container.createChild("div", "timeline-cpu-bars timeline-category-program");
900         var cpuBarsLabel = this._cpuBarsElement.createChild("span", "timeline-cpu-bars-label");
901         cpuBarsLabel.textContent = WebInspector.UIString("CPU");
902
903         const headerBorderWidth = 1;
904         const headerMargin = 2;
905
906         var headerHeight = this._headerLineCount * WebInspector.TimelinePanel.rowHeight;
907         this.sidebarElement.firstChild.style.height = headerHeight + "px";
908         this._timelineGrid.dividersLabelBarElement.style.height = headerHeight + headerMargin + "px";
909         this._itemsGraphsElement.style.top = headerHeight + headerBorderWidth + "px";
910
911         this._mainThreadMonitoringEnabled = true;
912     },
913
914     _adjustScrollPosition: function(totalHeight)
915     {
916         // Prevent the container from being scrolled off the end.
917         if ((this._scrollTop + this._containerElementHeight) > totalHeight + 1)
918             this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight);
919     },
920
921     _getPopoverAnchor: function(element)
922     {
923         return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") ||
924             element.enclosingNodeOrSelfWithClass("timeline-tree-item") ||
925             element.enclosingNodeOrSelfWithClass("timeline-frame-strip");
926     },
927
928     _mouseOut: function(e)
929     {
930         this._hideRectHighlight();
931     },
932
933     _mouseMove: function(e)
934     {
935         var anchor = this._getPopoverAnchor(e.target);
936
937         if (anchor && anchor.row && anchor.row._record.type === "Paint")
938             this._highlightRect(anchor.row._record);
939         else
940             this._hideRectHighlight();
941     },
942
943     _highlightRect: function(record)
944     {
945         if (this._highlightedRect === record.data)
946             return;
947         this._highlightedRect = record.data;
948         DOMAgent.highlightRect(this._highlightedRect.x, this._highlightedRect.y, this._highlightedRect.width, this._highlightedRect.height, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA());
949     },
950
951     _hideRectHighlight: function()
952     {
953         if (this._highlightedRect) {
954             delete this._highlightedRect;
955             DOMAgent.hideHighlight();
956         }
957     },
958
959     /**
960      * @param {Element} anchor
961      * @param {WebInspector.Popover} popover
962      */
963     _showPopover: function(anchor, popover)
964     {
965         if (anchor.hasStyleClass("timeline-frame-strip")) {
966             var frame = anchor._frame;
967             popover.show(WebInspector.TimelinePresentationModel.generatePopupContentForFrame(frame), anchor);
968         } else {
969             if (anchor.row && anchor.row._record)
970                 anchor.row._record.generatePopupContent(showCallback);
971         }
972
973         function showCallback(popupContent)
974         {
975             popover.show(popupContent, anchor);
976         }
977     },
978
979     _closeRecordDetails: function()
980     {
981         this._popoverHelper.hidePopover();
982     },
983
984     _injectCategoryStyles: function()
985     {
986         var style = document.createElement("style");
987         var categories = WebInspector.TimelinePresentationModel.categories();
988
989         style.textContent = Object.values(categories).map(WebInspector.TimelinePresentationModel.createStyleRuleForCategory).join("\n");
990         document.head.appendChild(style);
991     }
992 }
993
994 WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype;
995
996 /**
997  * @constructor
998  * @param {WebInspector.TimelineModel} model
999  */
1000 WebInspector.TimelineCalculator = function(model)
1001 {
1002     this._model = model;
1003 }
1004
1005 WebInspector.TimelineCalculator._minWidth = 5;
1006
1007 WebInspector.TimelineCalculator.prototype = {
1008     /**
1009      * @param {number} time
1010      */
1011     computePosition: function(time)
1012     {
1013         return (time - this.minimumBoundary) / this.boundarySpan * this._workingArea + this.paddingLeft;
1014     },
1015
1016     computeBarGraphPercentages: function(record)
1017     {
1018         var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100;
1019         var end = (record.startTime + record.selfTime - this.minimumBoundary) / this.boundarySpan * 100;
1020         var endWithChildren = (record.lastChildEndTime - this.minimumBoundary) / this.boundarySpan * 100;
1021         var cpuWidth = record.cpuTime / this.boundarySpan * 100;
1022         return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth};
1023     },
1024
1025     computeBarGraphWindowPosition: function(record)
1026     {
1027         var percentages = this.computeBarGraphPercentages(record);
1028         var widthAdjustment = 0;
1029
1030         var left = this.computePosition(record.startTime);
1031         var width = (percentages.end - percentages.start) / 100 * this._workingArea;
1032         if (width < WebInspector.TimelineCalculator._minWidth) {
1033             widthAdjustment = WebInspector.TimelineCalculator._minWidth - width;
1034             left -= widthAdjustment / 2;
1035             width += widthAdjustment;
1036         }
1037         var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * this._workingArea + widthAdjustment;
1038         var cpuWidth = percentages.cpuWidth / 100 * this._workingArea + widthAdjustment;
1039         if (percentages.endWithChildren > percentages.end)
1040             widthWithChildren += widthAdjustment;
1041         return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth};
1042     },
1043
1044     setWindow: function(minimumBoundary, maximumBoundary)
1045     {
1046         this.minimumBoundary = minimumBoundary;
1047         this.maximumBoundary = maximumBoundary;
1048         this.boundarySpan = this.maximumBoundary - this.minimumBoundary;
1049     },
1050
1051     /**
1052      * @param {number} paddingLeft
1053      * @param {number} clientWidth
1054      */
1055     setDisplayWindow: function(paddingLeft, clientWidth)
1056     {
1057         this._workingArea = clientWidth - WebInspector.TimelineCalculator._minWidth - paddingLeft;
1058         this.paddingLeft = paddingLeft;
1059     },
1060
1061     formatTime: function(value)
1062     {
1063         return Number.secondsToString(value + this.minimumBoundary - this._model.minimumRecordTime());
1064     }
1065 }
1066
1067 /**
1068  * @constructor
1069  */
1070 WebInspector.TimelineRecordListRow = function()
1071 {
1072     this.element = document.createElement("div");
1073     this.element.row = this;
1074     this.element.style.cursor = "pointer";
1075     var iconElement = document.createElement("span");
1076     iconElement.className = "timeline-tree-icon";
1077     this.element.appendChild(iconElement);
1078
1079     this._typeElement = document.createElement("span");
1080     this._typeElement.className = "type";
1081     this.element.appendChild(this._typeElement);
1082
1083     var separatorElement = document.createElement("span");
1084     separatorElement.className = "separator";
1085     separatorElement.textContent = " ";
1086
1087     this._dataElement = document.createElement("span");
1088     this._dataElement.className = "data dimmed";
1089
1090     this.element.appendChild(separatorElement);
1091     this.element.appendChild(this._dataElement);
1092 }
1093
1094 WebInspector.TimelineRecordListRow.prototype = {
1095     update: function(record, isEven, offset)
1096     {
1097         this._record = record;
1098         this._offset = offset;
1099
1100         this.element.className = "timeline-tree-item timeline-category-" + record.category.name + (isEven ? " even" : "");
1101         this._typeElement.textContent = record.title;
1102
1103         if (this._dataElement.firstChild)
1104             this._dataElement.removeChildren();
1105         var details = record.details();
1106         if (details) {
1107             var detailsContainer = document.createElement("span");
1108             if (typeof details === "object") {
1109                 detailsContainer.appendChild(document.createTextNode("("));
1110                 detailsContainer.appendChild(details);
1111                 detailsContainer.appendChild(document.createTextNode(")"));
1112             } else
1113                 detailsContainer.textContent = "(" + details + ")";
1114             this._dataElement.appendChild(detailsContainer);
1115         }
1116     },
1117
1118     dispose: function()
1119     {
1120         this.element.parentElement.removeChild(this.element);
1121     }
1122 }
1123
1124 /**
1125  * @constructor
1126  */
1127 WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh)
1128 {
1129     this.element = document.createElement("div");
1130     this.element.row = this;
1131
1132     this._barAreaElement = document.createElement("div");
1133     this._barAreaElement.className = "timeline-graph-bar-area";
1134     this.element.appendChild(this._barAreaElement);
1135
1136     this._barWithChildrenElement = document.createElement("div");
1137     this._barWithChildrenElement.className = "timeline-graph-bar with-children";
1138     this._barWithChildrenElement.row = this;
1139     this._barAreaElement.appendChild(this._barWithChildrenElement);
1140
1141     this._barCpuElement = document.createElement("div");
1142     this._barCpuElement.className = "timeline-graph-bar cpu"
1143     this._barCpuElement.row = this;
1144     this._barAreaElement.appendChild(this._barCpuElement);
1145
1146     this._barElement = document.createElement("div");
1147     this._barElement.className = "timeline-graph-bar";
1148     this._barElement.row = this;
1149     this._barAreaElement.appendChild(this._barElement);
1150
1151     this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer);
1152     this._expandElement._element.addEventListener("click", this._onClick.bind(this));
1153
1154     this._scheduleRefresh = scheduleRefresh;
1155 }
1156
1157 WebInspector.TimelineRecordGraphRow.prototype = {
1158     update: function(record, isEven, calculator, expandOffset, index)
1159     {
1160         this._record = record;
1161         this.element.className = "timeline-graph-side timeline-category-" + record.category.name + (isEven ? " even" : "");
1162         var barPosition = calculator.computeBarGraphWindowPosition(record);
1163         this._barWithChildrenElement.style.left = barPosition.left + "px";
1164         this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px";
1165         this._barElement.style.left = barPosition.left + "px";
1166         this._barElement.style.width = barPosition.width + "px";
1167         this._barCpuElement.style.left = barPosition.left + "px";
1168         this._barCpuElement.style.width = barPosition.cpuWidth + "px";
1169         this._expandElement._update(record, index, barPosition.left - expandOffset, barPosition.width);
1170     },
1171
1172     _onClick: function(event)
1173     {
1174         this._record.collapsed = !this._record.collapsed;
1175         this._scheduleRefresh(false);
1176     },
1177
1178     dispose: function()
1179     {
1180         this.element.parentElement.removeChild(this.element);
1181         this._expandElement._dispose();
1182     }
1183 }
1184
1185 /**
1186  * @constructor
1187  */
1188 WebInspector.TimelineExpandableElement = function(container)
1189 {
1190     this._element = document.createElement("div");
1191     this._element.className = "timeline-expandable";
1192
1193     var leftBorder = document.createElement("div");
1194     leftBorder.className = "timeline-expandable-left";
1195     this._element.appendChild(leftBorder);
1196
1197     container.appendChild(this._element);
1198 }
1199
1200 WebInspector.TimelineExpandableElement.prototype = {
1201     _update: function(record, index, left, width)
1202     {
1203         const rowHeight = WebInspector.TimelinePanel.rowHeight;
1204         if (record.visibleChildrenCount || record.invisibleChildrenCount) {
1205             this._element.style.top = index * rowHeight + "px";
1206             this._element.style.left = left + "px";
1207             this._element.style.width = Math.max(12, width + 25) + "px";
1208             if (!record.collapsed) {
1209                 this._element.style.height = (record.visibleChildrenCount + 1) * rowHeight + "px";
1210                 this._element.addStyleClass("timeline-expandable-expanded");
1211                 this._element.removeStyleClass("timeline-expandable-collapsed");
1212             } else {
1213                 this._element.style.height = rowHeight + "px";
1214                 this._element.addStyleClass("timeline-expandable-collapsed");
1215                 this._element.removeStyleClass("timeline-expandable-expanded");
1216             }
1217             this._element.removeStyleClass("hidden");
1218         } else
1219             this._element.addStyleClass("hidden");
1220     },
1221
1222     _dispose: function()
1223     {
1224         this._element.parentElement.removeChild(this._element);
1225     }
1226 }
1227
1228 /**
1229  * @constructor
1230  * @implements {WebInspector.TimelinePresentationModel.Filter}
1231  */
1232 WebInspector.TimelineCategoryFilter = function()
1233 {
1234 }
1235
1236 WebInspector.TimelineCategoryFilter.prototype = {
1237     /**
1238      * @param {WebInspector.TimelinePresentationModel.Record} record
1239      */
1240     accept: function(record)
1241     {
1242         return !record.category.hidden && record.type !== WebInspector.TimelineModel.RecordType.BeginFrame;
1243     }
1244 }
1245
1246 /**
1247  * @param {WebInspector.TimelinePanel} panel
1248  * @constructor
1249  * @implements {WebInspector.TimelinePresentationModel.Filter}
1250  */
1251 WebInspector.TimelineIsLongFilter = function(panel)
1252 {
1253     this._panel = panel;
1254 }
1255
1256 WebInspector.TimelineIsLongFilter.prototype = {
1257     /**
1258      * @param {WebInspector.TimelinePresentationModel.Record} record
1259      */
1260     accept: function(record)
1261     {
1262         return this._panel._showShortEvents || record.isLong();
1263     }
1264 }