2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 * Copyright (C) 2012 Intel Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 * @implements {WebInspector.Searchable}
35 * @extends {WebInspector.View}
36 * @param {!WebInspector.TimelinePanel} panel
37 * @param {!WebInspector.TimelineModel} model
38 * @param {!WebInspector.Setting} glueRecordsSetting
39 * @param {string} mode
41 WebInspector.TimelineView = function(panel, model, glueRecordsSetting, mode)
43 WebInspector.View.call(this);
44 this.element.classList.add("timeline-view");
45 this.element.classList.add("hbox");
49 this._currentMode = mode;
50 this._calculator = new WebInspector.TimelineCalculator(this._model);
51 this._model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onTimelineEventRecorded, this);
53 // Create presentation model.
54 this._presentationModel = new WebInspector.TimelinePresentationModel();
55 this._durationFilter = new WebInspector.TimelineIsLongFilter();
56 this._windowFilter = new WebInspector.TimelineWindowFilter();
58 this._presentationModel.addFilter(this._windowFilter);
59 this._presentationModel.addFilter(new WebInspector.TimelineCategoryFilter());
60 this._presentationModel.addFilter(this._durationFilter);
62 this._frameMode = mode === WebInspector.TimelinePanel.Mode.Frames;
63 this._boundariesAreValid = true;
66 this._searchableView = new WebInspector.SearchableView(this);
67 this._searchableView.element.classList.add("searchable-view");
69 this._recordsView = this._createRecordsView();
70 this._recordsView.addEventListener(WebInspector.SplitView.Events.SidebarSizeChanged, this._sidebarResized, this);
71 this._recordsView.show(this._searchableView.element);
72 this._searchableView.element.appendChild(this._timelineGrid.gridHeaderElement);
74 this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
76 this.element.addEventListener("mousemove", this._mouseMove.bind(this), false);
77 this.element.addEventListener("mouseout", this._mouseOut.bind(this), false);
78 this.element.addEventListener("keydown", this._keyDown.bind(this), false);
80 this._expandOffset = 15;
82 this._allRecordsCount = 0;
84 this._presentationModel.setGlueRecords(glueRecordsSetting.get());
85 this._glueRecordsSetting = glueRecordsSetting;
86 this._glueRecordsSetting.addChangeListener(this._onGlueRecordsSettingChanged, this);
88 if (mode === WebInspector.TimelinePanel.Mode.Frames) {
89 this.frameModel = new WebInspector.TimelineFrameModel(this._model);
90 this._presentationModel.setGlueRecords(false);
93 this._searchableView.show(this.element);
96 WebInspector.TimelineView.commonUIFilters = function()
98 var filters = WebInspector.TimelineView._commonUIFilters;
103 filters._textFilterUI = new WebInspector.TextFilterUI();
105 var durationOptions = [];
106 for (var presetIndex = 0; presetIndex < WebInspector.TimelinePanel.durationFilterPresetsMs.length; ++presetIndex) {
107 var durationMs = WebInspector.TimelinePanel.durationFilterPresetsMs[presetIndex];
108 var durationOption = {};
110 durationOption.label = WebInspector.UIString("All");
111 durationOption.title = WebInspector.UIString("Show all records");
113 durationOption.label = WebInspector.UIString("\u2265 %dms", durationMs);
114 durationOption.title = WebInspector.UIString("Hide records shorter than %dms", durationMs);
116 durationOption.value = durationMs;
117 durationOptions.push(durationOption);
119 filters._durationFilterUI = new WebInspector.ComboBoxFilterUI(durationOptions);
121 filters._categoryFiltersUI = {};
122 var categoryTypes = [];
123 var categories = WebInspector.TimelinePresentationModel.categories();
124 for (var categoryName in categories) {
125 var category = categories[categoryName];
126 if (category.overviewStripGroupIndex < 0)
128 var filter = new WebInspector.CheckboxFilterUI(category.name, category.title);
129 filters._categoryFiltersUI[category.name] = filter;
131 WebInspector.TimelineView._commonUIFilters = filters;
135 WebInspector.TimelineView.prototype = {
137 * @return {!WebInspector.SplitView}
139 _createRecordsView: function()
141 var recordsView = new WebInspector.SplitView(true, false, "timeline-split");
142 this._containerElement = recordsView.element;
143 this._containerElement.tabIndex = 0;
144 this._containerElement.id = "timeline-container";
145 this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false);
147 // Create records list in the records sidebar.
148 recordsView.sidebarElement().createChild("div", "timeline-records-title").textContent = WebInspector.UIString("RECORDS");
149 this._sidebarListElement = recordsView.sidebarElement().createChild("div", "timeline-records-list");
151 // Create grid in the records main area.
152 this._gridContainer = new WebInspector.ViewWithResizeCallback(this._onViewportResize.bind(this));
153 this._gridContainer.element.id = "resources-container-content";
154 this._gridContainer.show(recordsView.mainElement());
155 this._timelineGrid = new WebInspector.TimelineGrid();
156 this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
157 this._itemsGraphsElement.id = "timeline-graphs";
158 this._gridContainer.element.appendChild(this._timelineGrid.element);
159 this._timelineGrid.gridHeaderElement.id = "timeline-grid-header";
160 this._timelineGrid.gridHeaderElement.classList.add("fill");
162 // Create gap elements
163 this._topGapElement = this._itemsGraphsElement.createChild("div", "timeline-gap");
164 this._graphRowsElement = this._itemsGraphsElement.createChild("div");
165 this._bottomGapElement = this._itemsGraphsElement.createChild("div", "timeline-gap");
166 this._expandElements = this._itemsGraphsElement.createChild("div");
167 this._expandElements.id = "orphan-expand-elements";
169 // Create gpu tasks containers.
170 /** @type {!Array.<!TimelineAgent.TimelineEvent>} */
171 this._mainThreadTasks = ([]);
172 /** @type {!Array.<!TimelineAgent.TimelineEvent>} */
173 this._gpuTasks = ([]);
174 var utilizationStripsElement = this._timelineGrid.gridHeaderElement.createChild("div", "timeline-utilization-strips vbox");
175 this._cpuBarsElement = utilizationStripsElement.createChild("div", "timeline-utilization-strip");
176 if (WebInspector.experimentsSettings.gpuTimeline.isEnabled())
177 this._gpuBarsElement = utilizationStripsElement.createChild("div", "timeline-utilization-strip gpu");
183 * @return {!WebInspector.SearchableView}
185 searchableView: function()
187 return this._searchableView;
193 supportsGlueParentMode: function()
195 return !this._frameMode;
198 _onGlueRecordsSettingChanged: function()
200 this._presentationModel.setGlueRecords(this._glueRecordsSetting.get());
201 this._repopulateRecords();
206 return this._calculator;
210 * @param {!WebInspector.FilterBar} filterBar
213 createUIFilters: function(filterBar)
215 var filters = this._filters;
217 this._filters = WebInspector.TimelineView.commonUIFilters();
218 filters = this._filters;
220 filters._textFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._textFilterChanged, this);
221 filters._durationFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._durationFilterChanged, this);
222 for (var categoryName in filters._categoryFiltersUI)
223 filters._categoryFiltersUI[categoryName].addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._categoriesFilterChanged.bind(this, categoryName), this);
226 filterBar.addFilter(filters._textFilterUI);
227 filterBar.addFilter(filters._durationFilterUI);
228 for (var categoryName in filters._categoryFiltersUI)
229 filterBar.addFilter(filters._categoryFiltersUI[categoryName]);
234 _textFilterChanged: function(event)
236 var searchQuery = this._filters._textFilterUI.value();
237 this._presentationModel.setSearchFilter(null);
238 delete this._searchFilter;
240 function cleanRecord(record)
242 delete record.clicked;
244 WebInspector.TimelinePresentationModel.forAllRecords(this._presentationModel.rootRecord().children, cleanRecord);
246 this.searchCanceled();
248 this._searchFilter = new WebInspector.TimelineSearchFilter(createPlainTextSearchRegex(searchQuery, "i"));
249 this._presentationModel.setSearchFilter(this._searchFilter);
251 this._invalidateAndScheduleRefresh(true, true);
254 _durationFilterChanged: function()
256 var duration = this._filters._durationFilterUI.value();
257 var minimumRecordDuration = +duration / 1000.0;
258 this._durationFilter.setMinimumRecordDuration(minimumRecordDuration);
259 this._invalidateAndScheduleRefresh(true, true);
262 _categoriesFilterChanged: function(name, event)
264 var categories = WebInspector.TimelinePresentationModel.categories();
265 categories[name].hidden = !this._filters._categoryFiltersUI[name].checked();
266 this._invalidateAndScheduleRefresh(true, true);
269 _rootRecord: function()
271 return this._presentationModel.rootRecord();
274 _updateRecordsCounter: function(recordsInWindowCount)
276 this._panel.recordsCounter.setText(WebInspector.UIString("%d of %d records shown", recordsInWindowCount, this._allRecordsCount));
279 _updateFrameStatistics: function(frames)
281 this._lastFrameStatistics = frames.length ? new WebInspector.FrameStatistics(frames) : null;
284 _updateEventDividers: function()
286 this._timelineGrid.removeEventDividers();
287 var clientWidth = this._graphRowsElementWidth;
289 var eventDividerRecords = this._presentationModel.eventDividerRecords();
291 for (var i = 0; i < eventDividerRecords.length; ++i) {
292 var record = eventDividerRecords[i];
293 var positions = this._calculator.computeBarGraphWindowPosition(record);
294 var dividerPosition = Math.round(positions.left);
295 if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition])
297 var divider = WebInspector.TimelinePresentationModel.createEventDivider(record.type, record.title);
298 divider.style.left = dividerPosition + "px";
299 dividers[dividerPosition] = divider;
301 this._timelineGrid.addEventDividers(dividers);
304 _updateFrameBars: function(frames)
306 var clientWidth = this._graphRowsElementWidth;
307 if (this._frameContainer)
308 this._frameContainer.removeChildren();
310 const frameContainerBorderWidth = 1;
311 this._frameContainer = document.createElement("div");
312 this._frameContainer.classList.add("fill");
313 this._frameContainer.classList.add("timeline-frame-container");
314 this._frameContainer.style.height = WebInspector.TimelinePanel.rowHeight + frameContainerBorderWidth + "px";
315 this._frameContainer.addEventListener("dblclick", this._onFrameDoubleClicked.bind(this), false);
320 for (var i = 0; i < frames.length; ++i) {
321 var frame = frames[i];
322 var frameStart = this._calculator.computePosition(frame.startTime);
323 var frameEnd = this._calculator.computePosition(frame.endTime);
325 var frameStrip = document.createElement("div");
326 frameStrip.className = "timeline-frame-strip";
327 var actualStart = Math.max(frameStart, 0);
328 var width = frameEnd - actualStart;
329 frameStrip.style.left = actualStart + "px";
330 frameStrip.style.width = width + "px";
331 frameStrip._frame = frame;
333 const minWidthForFrameInfo = 60;
334 if (width > minWidthForFrameInfo)
335 frameStrip.textContent = Number.secondsToString(frame.endTime - frame.startTime, true);
337 this._frameContainer.appendChild(frameStrip);
339 if (actualStart > 0) {
340 var frameMarker = WebInspector.TimelinePresentationModel.createEventDivider(WebInspector.TimelineModel.RecordType.BeginFrame);
341 frameMarker.style.left = frameStart + "px";
342 dividers.push(frameMarker);
345 this._timelineGrid.addEventDividers(dividers);
346 this._timelineGrid.gridHeaderElement.appendChild(this._frameContainer);
350 * @param {number} startTime
351 * @param {number} endTime
352 * @return {!Array.<!WebInspector.TimelineFrame>}
354 _filteredFrames: function(startTime, endTime)
356 if (!this.frameModel)
358 function compareStartTime(value, object)
360 return value - object.startTime;
362 function compareEndTime(value, object)
364 return value - object.endTime;
366 var frames = this.frameModel.frames();
367 var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, frames, compareStartTime);
368 var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, frames, compareEndTime);
369 while (lastFrame < frames.length && frames[lastFrame].endTime <= endTime)
371 return frames.slice(firstFrame, lastFrame);
375 _onFrameDoubleClicked: function(event)
377 var frameBar = event.target.enclosingNodeOrSelfWithClass("timeline-frame-strip");
380 this._panel.setWindowTimes(frameBar._frame.startTime, frameBar._frame.endTime);
383 _repopulateRecords: function()
386 this._automaticallySizeWindow = false;
387 var records = this._model.records;
388 for (var i = 0; i < records.length; ++i)
389 this._innerAddRecordToTimeline(records[i]);
390 this._invalidateAndScheduleRefresh(false, false);
393 _onTimelineEventRecorded: function(event)
395 if (this._innerAddRecordToTimeline(/** @type {!TimelineAgent.TimelineEvent} */(event.data)))
396 this._invalidateAndScheduleRefresh(false, false);
400 * @param {!TimelineAgent.TimelineEvent} record
403 _innerAddRecordToTimeline: function(record)
405 if (record.type === WebInspector.TimelineModel.RecordType.Program)
406 this._mainThreadTasks.push(record);
408 if (record.type === WebInspector.TimelineModel.RecordType.GPUTask) {
409 this._gpuTasks.push(record);
410 return WebInspector.TimelineModel.startTimeInSeconds(record) < this._panel.windowEndTime();
413 var records = this._presentationModel.addRecord(record);
414 this._allRecordsCount += records.length;
415 var hasVisibleRecords = false;
416 var presentationModel = this._presentationModel;
417 function checkVisible(record)
419 hasVisibleRecords |= presentationModel.isVisible(record);
421 WebInspector.TimelinePresentationModel.forAllRecords(records, checkVisible);
423 function isAdoptedRecord(record)
425 return record.parent !== presentationModel.rootRecord;
427 // Tell caller update is necessary either if we added a visible record or if we re-parented a record.
428 return hasVisibleRecords || records.some(isAdoptedRecord);
432 * @param {number} width
434 setSidebarSize: function(width)
436 this._recordsView.setSidebarSize(width);
440 * @param {!WebInspector.Event} event
442 _sidebarResized: function(event)
444 this.dispatchEventToListeners(WebInspector.SplitView.Events.SidebarSizeChanged, event.data);
447 _onViewportResize: function()
449 this._resize(this._recordsView.sidebarSize());
453 * @param {number} sidebarWidth
455 _resize: function(sidebarWidth)
457 this._closeRecordDetails();
458 this._graphRowsElementWidth = this._graphRowsElement.offsetWidth;
459 this._containerElementHeight = this._containerElement.clientHeight;
460 this._timelineGrid.gridHeaderElement.style.left = sidebarWidth + "px";
461 this._timelineGrid.gridHeaderElement.style.width = this._itemsGraphsElement.offsetWidth + "px";
462 this._scheduleRefresh(false, true);
465 _resetView: function()
467 this._presentationModel.reset();
468 this._boundariesAreValid = false;
469 this._adjustScrollPosition(0);
470 this._closeRecordDetails();
471 this._allRecordsCount = 0;
472 this._automaticallySizeWindow = true;
473 this._mainThreadTasks = [];
480 this._windowFilter.reset();
481 this._invalidateAndScheduleRefresh(true, true);
482 this._updateSelectionDetails();
486 * @return {!Array.<!Element>}
488 elementsToRestoreScrollPositionsFor: function()
490 return [this._containerElement];
495 WebInspector.View.prototype.wasShown.call(this);
497 this._repopulateRecords();
498 this._updateSelectionDetails();
500 if (!WebInspector.TimelinePanel._categoryStylesInitialized) {
501 WebInspector.TimelinePanel._categoryStylesInitialized = true;
502 this._injectCategoryStyles();
504 this._onViewportResize();
510 this._closeRecordDetails();
511 WebInspector.View.prototype.willHide.call(this);
514 _onScroll: function(event)
516 this._closeRecordDetails();
517 this._scrollTop = this._containerElement.scrollTop;
518 var dividersTop = Math.max(0, this._scrollTop);
519 this._timelineGrid.setScrollAndDividerTop(this._scrollTop, dividersTop);
520 this._scheduleRefresh(true, true);
524 * @param {boolean} preserveBoundaries
525 * @param {boolean} userGesture
527 _invalidateAndScheduleRefresh: function(preserveBoundaries, userGesture)
529 this._presentationModel.invalidateFilteredRecords();
530 delete this._searchResults;
531 this._scheduleRefresh(preserveBoundaries, userGesture);
535 * @param {?WebInspector.TimelinePresentationModel.Record} record
537 _selectRecord: function(record)
539 if (record === this._lastSelectedRecord)
542 // Remove selection rendering.
543 if (this._lastSelectedRecord) {
544 var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (this._lastSelectedRecord.getUserObject("WebInspector.TimelineRecordListRow"));
546 listRow.renderAsSelected(false);
547 var graphRow = /** @type {!WebInspector.TimelineRecordGraphRow} */ (this._lastSelectedRecord.getUserObject("WebInspector.TimelineRecordGraphRow"));
549 graphRow.renderAsSelected(false);
553 this._updateSelectionDetails();
557 this._lastSelectedRecord = record;
558 this._revealRecord(record);
559 var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (record.getUserObject("WebInspector.TimelineRecordListRow"));
561 listRow.renderAsSelected(true);
562 var graphRow = /** @type {!WebInspector.TimelineRecordListRow} */ (record.getUserObject("WebInspector.TimelineRecordGraphRow"));
564 graphRow.renderAsSelected(true);
566 record.generatePopupContent(showCallback.bind(this));
569 * @param {!DocumentFragment} element
570 * @this {WebInspector.TimelineView}
572 function showCallback(element)
574 this._panel.setDetailsContent(record.title, element);
578 _updateSelectionDetails: function()
580 var startTime = this._panel.windowStartTime() * 1000;
581 var endTime = this._panel.windowEndTime() * 1000;
582 // Return early in case 0 selection window.
586 var aggregatedStats = {};
589 * @param {number} value
590 * @param {!TimelineAgent.TimelineEvent} task
593 function compareEndTime(value, task)
595 return value < task.endTime ? -1 : 1;
599 * @param {!TimelineAgent.TimelineEvent} rawRecord
601 function aggregateTimeForRecordWithinWindow(rawRecord)
603 if (!rawRecord.endTime || rawRecord.endTime < startTime || rawRecord.startTime > endTime)
606 var childrenTime = 0;
607 var children = rawRecord.children || [];
608 for (var i = 0; i < children.length; ++i) {
609 var child = children[i];
610 if (!child.endTime || child.endTime < startTime || child.startTime > endTime)
612 childrenTime += Math.min(endTime, child.endTime) - Math.max(startTime, child.startTime);
613 aggregateTimeForRecordWithinWindow(child);
615 var categoryName = WebInspector.TimelinePresentationModel.categoryForRecord(rawRecord).name;
616 var ownTime = Math.min(endTime, rawRecord.endTime) - Math.max(startTime, rawRecord.startTime) - childrenTime;
617 aggregatedStats[categoryName] = (aggregatedStats[categoryName] || 0) + ownTime / 1000;
620 var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, this._mainThreadTasks, compareEndTime);
621 for (; taskIndex < this._mainThreadTasks.length; ++taskIndex) {
622 var task = this._mainThreadTasks[taskIndex];
623 if (task.startTime > endTime)
625 aggregateTimeForRecordWithinWindow(task);
628 var aggregatedTotal = 0;
629 for (var categoryName in aggregatedStats)
630 aggregatedTotal += aggregatedStats[categoryName];
631 aggregatedStats["idle"] = Math.max(0, (endTime - startTime) / 1000 - aggregatedTotal);
633 var fragment = document.createDocumentFragment();
634 fragment.appendChild(WebInspector.TimelinePresentationModel.generatePieChart(aggregatedStats));
636 if (this._frameMode && this._lastFrameStatistics) {
637 var title = WebInspector.UIString("%s \u2013 %s (%d frames)", Number.secondsToString(this._lastFrameStatistics.startOffset, true), Number.secondsToString(this._lastFrameStatistics.endOffset, true), this._lastFrameStatistics.frameCount);
638 fragment.appendChild(WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics(this._lastFrameStatistics));
640 var title = WebInspector.UIString("%s \u2013 %s", this._calculator.formatTime(0, true), this._calculator.formatTime(this._calculator.boundarySpan(), true));
642 this._panel.setDetailsContent(title, fragment);
646 * @param {number} startTime
647 * @param {number} endTime
649 setWindowTimes: function(startTime, endTime)
651 this._windowFilter.setWindowTimes(startTime, endTime);
652 this._invalidateAndScheduleRefresh(false, true);
653 this._selectRecord(null);
657 * @param {boolean} preserveBoundaries
658 * @param {boolean} userGesture
660 _scheduleRefresh: function(preserveBoundaries, userGesture)
662 this._closeRecordDetails();
663 this._boundariesAreValid &= preserveBoundaries;
665 if (!this.isShowing())
668 if (preserveBoundaries || userGesture)
671 if (!this._refreshTimeout)
672 this._refreshTimeout = setTimeout(this._refresh.bind(this), 300);
678 if (this._refreshTimeout) {
679 clearTimeout(this._refreshTimeout);
680 delete this._refreshTimeout;
682 var windowStartTime = this._panel.windowStartTime();
683 var windowEndTime = this._panel.windowEndTime();
684 this._timelinePaddingLeft = this._expandOffset;
685 this._calculator.setWindow(windowStartTime, windowEndTime);
686 this._calculator.setDisplayWindow(this._timelinePaddingLeft, this._graphRowsElementWidth);
688 var recordsInWindowCount = this._refreshRecords();
689 this._updateRecordsCounter(recordsInWindowCount);
690 if (!this._boundariesAreValid) {
691 this._updateEventDividers();
692 var frames = this._filteredFrames(windowStartTime, windowEndTime);
694 this._updateFrameStatistics(frames);
695 const maxFramesForFrameBars = 30;
696 if (frames.length && frames.length < maxFramesForFrameBars) {
697 this._timelineGrid.removeDividers();
698 this._updateFrameBars(frames);
700 if (this._frameContainer)
701 this._frameContainer.remove();
702 this._timelineGrid.updateDividers(this._calculator);
705 this._timelineGrid.updateDividers(this._calculator);
706 this._refreshAllUtilizationBars();
708 this._boundariesAreValid = true;
711 revealRecordAt: function(time)
714 function findRecordToReveal(record)
716 if (record.containsTime(time)) {
717 recordToReveal = record;
720 // If there is no record containing the time than use the latest one before that time.
721 if (!recordToReveal || record.endTime < time && recordToReveal.endTime < record.endTime)
722 recordToReveal = record;
725 WebInspector.TimelinePresentationModel.forAllRecords(this._presentationModel.rootRecord().children, null, findRecordToReveal);
727 // The record ends before the window left bound so scroll to the top.
728 if (!recordToReveal) {
729 this._containerElement.scrollTop = 0;
733 this._selectRecord(recordToReveal);
737 * @param {!WebInspector.TimelinePresentationModel.Record} recordToReveal
739 _revealRecord: function(recordToReveal)
741 var needRefresh = false;
742 // Expand all ancestors.
743 for (var parent = recordToReveal.parent; parent !== this._rootRecord(); parent = parent.parent) {
744 if (!parent.collapsed)
746 this._presentationModel.invalidateFilteredRecords();
747 parent.collapsed = false;
750 var recordsInWindow = this._presentationModel.filteredRecords();
751 var index = recordsInWindow.indexOf(recordToReveal);
753 var itemOffset = index * WebInspector.TimelinePanel.rowHeight;
754 var visibleTop = this._scrollTop - WebInspector.TimelinePanel.headerHeight;
755 var visibleBottom = visibleTop + this._containerElementHeight - WebInspector.TimelinePanel.rowHeight;
756 if (itemOffset < visibleTop)
757 this._containerElement.scrollTop = itemOffset;
758 else if (itemOffset > visibleBottom)
759 this._containerElement.scrollTop = itemOffset - this._containerElementHeight + WebInspector.TimelinePanel.headerHeight + WebInspector.TimelinePanel.rowHeight;
760 else if (needRefresh)
761 this._refreshRecords();
764 _refreshRecords: function()
766 var recordsInWindow = this._presentationModel.filteredRecords();
768 // Calculate the visible area.
769 var visibleTop = this._scrollTop;
770 var visibleBottom = visibleTop + this._containerElementHeight;
772 var rowHeight = WebInspector.TimelinePanel.rowHeight;
773 var headerHeight = WebInspector.TimelinePanel.headerHeight;
775 // Convert visible area to visible indexes. Always include top-level record for a visible nested record.
776 var startIndex = Math.max(0, Math.min(Math.floor((visibleTop - headerHeight) / rowHeight), recordsInWindow.length - 1));
777 var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));
778 var lastVisibleLine = Math.max(0, Math.floor((visibleBottom - headerHeight) / rowHeight));
779 if (this._automaticallySizeWindow && recordsInWindow.length > lastVisibleLine) {
780 this._automaticallySizeWindow = false;
781 this._selectRecord(null);
782 // If we're at the top, always use real timeline start as a left window bound so that expansion arrow padding logic works.
783 var windowStartTime = startIndex ? recordsInWindow[startIndex].startTime : this._model.minimumRecordTime();
784 var windowEndTime = recordsInWindow[Math.max(0, lastVisibleLine - 1)].endTime;
785 this._panel.setWindowTimes(windowStartTime, windowEndTime);
786 this._windowFilter.setWindowTimes(windowStartTime, windowEndTime);
787 recordsInWindow = this._presentationModel.filteredRecords();
788 endIndex = Math.min(recordsInWindow.length, lastVisibleLine);
791 // Resize gaps first.
792 this._topGapElement.style.height = (startIndex * rowHeight) + "px";
793 this._recordsView.sidebarElement().firstElementChild.style.flexBasis = (startIndex * rowHeight + headerHeight) + "px";
794 this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px";
795 var rowsHeight = headerHeight + recordsInWindow.length * rowHeight;
796 var totalHeight = Math.max(this._containerElementHeight, rowsHeight);
798 this._recordsView.mainElement().style.height = totalHeight + "px";
799 this._recordsView.sidebarElement().style.height = totalHeight + "px";
800 this._recordsView.resizerElement().style.height = totalHeight + "px";
802 // Update visible rows.
803 var listRowElement = this._sidebarListElement.firstChild;
804 var width = this._graphRowsElementWidth;
805 this._itemsGraphsElement.removeChild(this._graphRowsElement);
806 var graphRowElement = this._graphRowsElement.firstChild;
807 var scheduleRefreshCallback = this._invalidateAndScheduleRefresh.bind(this, true, true);
808 var selectRecordCallback = this._selectRecord.bind(this);
809 this._itemsGraphsElement.removeChild(this._expandElements);
810 this._expandElements.removeChildren();
812 for (var i = 0; i < endIndex; ++i) {
813 var record = recordsInWindow[i];
815 if (i < startIndex) {
816 var lastChildIndex = i + record.visibleChildrenCount;
817 if (lastChildIndex >= startIndex && lastChildIndex < endIndex) {
818 var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements);
819 var positions = this._calculator.computeBarGraphWindowPosition(record);
820 expandElement._update(record, i, positions.left - this._expandOffset, positions.width);
823 if (!listRowElement) {
824 listRowElement = new WebInspector.TimelineRecordListRow(selectRecordCallback, scheduleRefreshCallback).element;
825 this._sidebarListElement.appendChild(listRowElement);
827 if (!graphRowElement) {
828 graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, selectRecordCallback, scheduleRefreshCallback).element;
829 this._graphRowsElement.appendChild(graphRowElement);
832 listRowElement.row.update(record, visibleTop);
833 graphRowElement.row.update(record, this._calculator, this._expandOffset, i);
834 if (this._lastSelectedRecord === record) {
835 listRowElement.row.renderAsSelected(true);
836 graphRowElement.row.renderAsSelected(true);
839 listRowElement = listRowElement.nextSibling;
840 graphRowElement = graphRowElement.nextSibling;
844 // Remove extra rows.
845 while (listRowElement) {
846 var nextElement = listRowElement.nextSibling;
847 listRowElement.row.dispose();
848 listRowElement = nextElement;
850 while (graphRowElement) {
851 var nextElement = graphRowElement.nextSibling;
852 graphRowElement.row.dispose();
853 graphRowElement = nextElement;
856 this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement);
857 this._itemsGraphsElement.appendChild(this._expandElements);
858 this._adjustScrollPosition(recordsInWindow.length * rowHeight + headerHeight);
859 this._updateSearchHighlight(false, true);
861 return recordsInWindow.length;
864 _refreshAllUtilizationBars: function()
866 this._refreshUtilizationBars(WebInspector.UIString("CPU"), this._mainThreadTasks, this._cpuBarsElement);
867 if (WebInspector.experimentsSettings.gpuTimeline.isEnabled())
868 this._refreshUtilizationBars(WebInspector.UIString("GPU"), this._gpuTasks, this._gpuBarsElement);
872 * @param {string} name
873 * @param {!Array.<!TimelineAgent.TimelineEvent>} tasks
874 * @param {?Element} container
876 _refreshUtilizationBars: function(name, tasks, container)
884 var minWidth = WebInspector.TimelineCalculator._minWidth;
885 var widthAdjustment = minWidth / 2;
887 var width = this._graphRowsElementWidth;
888 var boundarySpan = this._panel.windowEndTime() - this._panel.windowStartTime();
889 var scale = boundarySpan / (width - minWidth - this._timelinePaddingLeft);
890 var startTime = (this._panel.windowStartTime() - this._timelinePaddingLeft * scale) * 1000;
891 var endTime = startTime + width * scale * 1000;
894 * @param {number} value
895 * @param {!TimelineAgent.TimelineEvent} task
898 function compareEndTime(value, task)
900 return value < task.endTime ? -1 : 1;
903 var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, tasks, compareEndTime);
905 var foreignStyle = "gpu-task-foreign";
906 var element = container.firstChild;
911 for (; taskIndex < tasks.length; ++taskIndex) {
912 var task = tasks[taskIndex];
913 if (task.startTime > endTime)
916 var left = Math.max(0, this._calculator.computePosition(WebInspector.TimelineModel.startTimeInSeconds(task)) + barOffset - widthAdjustment);
917 var right = Math.min(width, this._calculator.computePosition(WebInspector.TimelineModel.endTimeInSeconds(task)) + barOffset + widthAdjustment);
920 var gap = Math.floor(left) - Math.ceil(lastRight);
922 if (!task.data["foreign"])
923 lastElement.classList.remove(foreignStyle);
925 lastElement._tasksInfo.lastTaskIndex = taskIndex;
928 lastElement.style.width = (lastRight - lastLeft) + "px";
932 element = container.createChild("div", "timeline-graph-bar");
933 element.style.left = left + "px";
934 element._tasksInfo = {name: name, tasks: tasks, firstTaskIndex: taskIndex, lastTaskIndex: taskIndex};
935 if (task.data["foreign"])
936 element.classList.add(foreignStyle);
939 lastElement = element;
940 element = element.nextSibling;
944 lastElement.style.width = (lastRight - lastLeft) + "px";
947 var nextElement = element.nextSibling;
948 element._tasksInfo = null;
949 container.removeChild(element);
950 element = nextElement;
954 _adjustScrollPosition: function(totalHeight)
956 // Prevent the container from being scrolled off the end.
957 if ((this._scrollTop + this._containerElementHeight) > totalHeight + 1)
958 this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight);
961 _getPopoverAnchor: function(element)
963 var anchor = element.enclosingNodeOrSelfWithClass("timeline-graph-bar");
964 if (anchor && anchor._tasksInfo)
966 return element.enclosingNodeOrSelfWithClass("timeline-frame-strip");
969 _mouseOut: function()
971 this._hideQuadHighlight();
977 _mouseMove: function(e)
979 var rowElement = e.target.enclosingNodeOrSelfWithClass("timeline-tree-item");
980 if (rowElement && rowElement.row && rowElement.row._record.highlightQuad)
981 this._highlightQuad(rowElement.row._record.highlightQuad);
983 this._hideQuadHighlight();
985 var taskBarElement = e.target.enclosingNodeOrSelfWithClass("timeline-graph-bar");
986 if (taskBarElement && taskBarElement._tasksInfo) {
987 var offset = taskBarElement.offsetLeft;
988 this._timelineGrid.showCurtains(offset >= 0 ? offset : 0, taskBarElement.offsetWidth);
990 this._timelineGrid.hideCurtains();
994 * @param {?Event} event
996 _keyDown: function(event)
998 if (!this._lastSelectedRecord || event.shiftKey || event.metaKey || event.ctrlKey)
1001 var record = this._lastSelectedRecord;
1002 var recordsInWindow = this._presentationModel.filteredRecords();
1003 var index = recordsInWindow.indexOf(record);
1004 var recordsInPage = Math.floor(this._containerElementHeight / WebInspector.TimelinePanel.rowHeight);
1005 var rowHeight = WebInspector.TimelinePanel.rowHeight;
1010 switch (event.keyIdentifier) {
1012 if (record.parent) {
1013 if ((!record.expandable || record.collapsed) && record.parent !== this._presentationModel.rootRecord()) {
1014 this._selectRecord(record.parent);
1016 record.collapsed = true;
1017 record.clicked = true;
1018 this._invalidateAndScheduleRefresh(true, true);
1021 event.consume(true);
1026 this._selectRecord(recordsInWindow[index]);
1027 event.consume(true);
1030 if (record.expandable && record.collapsed) {
1031 record.collapsed = false;
1032 record.clicked = true;
1033 this._invalidateAndScheduleRefresh(true, true);
1035 if (++index >= recordsInWindow.length)
1037 this._selectRecord(recordsInWindow[index]);
1039 event.consume(true);
1042 if (++index >= recordsInWindow.length)
1044 this._selectRecord(recordsInWindow[index]);
1045 event.consume(true);
1048 index = Math.max(0, index - recordsInPage);
1049 this._scrollTop = Math.max(0, this._scrollTop - recordsInPage * rowHeight);
1050 this._containerElement.scrollTop = this._scrollTop;
1051 this._selectRecord(recordsInWindow[index]);
1052 event.consume(true);
1055 index = Math.min(recordsInWindow.length - 1, index + recordsInPage);
1056 this._scrollTop = Math.min(this._containerElement.scrollHeight - this._containerElementHeight, this._scrollTop + recordsInPage * rowHeight);
1057 this._containerElement.scrollTop = this._scrollTop;
1058 this._selectRecord(recordsInWindow[index]);
1059 event.consume(true);
1063 this._selectRecord(recordsInWindow[index]);
1064 event.consume(true);
1067 index = recordsInWindow.length - 1;
1068 this._selectRecord(recordsInWindow[index]);
1069 event.consume(true);
1075 * @param {!Array.<number>} quad
1077 _highlightQuad: function(quad)
1079 if (this._highlightedQuad === quad)
1081 this._highlightedQuad = quad;
1082 DOMAgent.highlightQuad(quad, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA());
1085 _hideQuadHighlight: function()
1087 if (this._highlightedQuad) {
1088 delete this._highlightedQuad;
1089 DOMAgent.hideHighlight();
1094 * @param {!Element} anchor
1095 * @param {!WebInspector.Popover} popover
1097 _showPopover: function(anchor, popover)
1099 if (anchor.classList.contains("timeline-frame-strip")) {
1100 var frame = anchor._frame;
1101 popover.show(WebInspector.TimelinePresentationModel.generatePopupContentForFrame(frame), anchor);
1103 if (anchor.row && anchor.row._record)
1104 anchor.row._record.generatePopupContent(showCallback);
1105 else if (anchor._tasksInfo)
1106 popover.show(this._presentationModel.generateMainThreadBarPopupContent(anchor._tasksInfo), anchor, null, null, WebInspector.Popover.Orientation.Bottom);
1109 function showCallback(popupContent)
1111 popover.show(popupContent, anchor);
1115 _closeRecordDetails: function()
1117 this._popoverHelper.hidePopover();
1120 _injectCategoryStyles: function()
1122 var style = document.createElement("style");
1123 var categories = WebInspector.TimelinePresentationModel.categories();
1125 style.textContent = Object.values(categories).map(WebInspector.TimelinePresentationModel.createStyleRuleForCategory).join("\n");
1126 document.head.appendChild(style);
1129 jumpToNextSearchResult: function()
1131 if (!this._searchResults || !this._searchResults.length)
1133 var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : -1;
1134 this._jumpToSearchResult(index + 1);
1137 jumpToPreviousSearchResult: function()
1139 if (!this._searchResults || !this._searchResults.length)
1141 var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : 0;
1142 this._jumpToSearchResult(index - 1);
1145 _jumpToSearchResult: function(index)
1147 this._selectSearchResult((index + this._searchResults.length) % this._searchResults.length);
1148 this._highlightSelectedSearchResult(true);
1151 _selectSearchResult: function(index)
1153 this._selectedSearchResult = this._searchResults[index];
1154 this._searchableView.updateCurrentMatchIndex(index);
1158 * @param {boolean} selectRecord
1160 _highlightSelectedSearchResult: function(selectRecord)
1162 this._clearHighlight();
1163 if (this._searchFilter)
1166 var record = this._selectedSearchResult;
1171 this._selectRecord(record);
1173 for (var element = this._sidebarListElement.firstChild; element; element = element.nextSibling) {
1174 if (element.row._record === record) {
1175 element.row.highlight(this._searchRegExp, this._highlightDomChanges);
1181 _clearHighlight: function()
1183 if (this._highlightDomChanges)
1184 WebInspector.revertDomChanges(this._highlightDomChanges);
1185 this._highlightDomChanges = [];
1189 * @param {boolean} revealRecord
1190 * @param {boolean} shouldJump
1192 _updateSearchHighlight: function(revealRecord, shouldJump)
1194 if (this._searchFilter || !this._searchRegExp) {
1195 this._clearHighlight();
1199 if (!this._searchResults)
1200 this._updateSearchResults(shouldJump);
1201 this._highlightSelectedSearchResult(revealRecord);
1204 _updateSearchResults: function(shouldJump)
1206 var searchRegExp = this._searchRegExp;
1211 var presentationModel = this._presentationModel;
1213 function processRecord(record)
1215 if (presentationModel.isVisible(record) && WebInspector.TimelineRecordListRow.testContentMatching(record, searchRegExp))
1216 matches.push(record);
1219 WebInspector.TimelinePresentationModel.forAllRecords(presentationModel.rootRecord().children, processRecord);
1221 var matchesCount = matches.length;
1223 this._searchResults = matches;
1224 this._searchableView.updateSearchMatchesCount(matchesCount);
1226 var selectedIndex = matches.indexOf(this._selectedSearchResult);
1227 if (shouldJump && selectedIndex === -1)
1229 this._selectSearchResult(selectedIndex);
1231 this._searchableView.updateSearchMatchesCount(0);
1232 delete this._selectedSearchResult;
1236 searchCanceled: function()
1238 this._clearHighlight();
1239 delete this._searchResults;
1240 delete this._selectedSearchResult;
1241 delete this._searchRegExp;
1245 * @param {string} query
1246 * @param {boolean} shouldJump
1248 performSearch: function(query, shouldJump)
1250 this._searchRegExp = createPlainTextSearchRegex(query, "i");
1251 delete this._searchResults;
1252 this._updateSearchHighlight(true, shouldJump);
1255 __proto__: WebInspector.View.prototype
1260 * @param {!WebInspector.TimelineModel} model
1261 * @implements {WebInspector.TimelineGrid.Calculator}
1263 WebInspector.TimelineCalculator = function(model)
1265 this._model = model;
1268 WebInspector.TimelineCalculator._minWidth = 5;
1270 WebInspector.TimelineCalculator.prototype = {
1272 * @param {number} time
1275 computePosition: function(time)
1277 return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this.paddingLeft;
1281 * @return {!{start: number, end: number, endWithChildren: number, cpuWidth: number}}
1283 computeBarGraphPercentages: function(record)
1285 var start = (record.startTime - this._minimumBoundary) / this.boundarySpan() * 100;
1286 var end = (record.startTime + record.selfTime - this._minimumBoundary) / this.boundarySpan() * 100;
1287 var endWithChildren = (record.lastChildEndTime - this._minimumBoundary) / this.boundarySpan() * 100;
1288 var cpuWidth = record.coalesced ? endWithChildren - start : record.cpuTime / this.boundarySpan() * 100;
1289 return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth};
1293 * @return {!{left: number, width: number, widthWithChildren: number, cpuWidth: number}}
1295 computeBarGraphWindowPosition: function(record)
1297 var percentages = this.computeBarGraphPercentages(record);
1298 var widthAdjustment = 0;
1300 var left = this.computePosition(record.startTime);
1301 var width = (percentages.end - percentages.start) / 100 * this._workingArea;
1302 if (width < WebInspector.TimelineCalculator._minWidth) {
1303 widthAdjustment = WebInspector.TimelineCalculator._minWidth - width;
1304 width = WebInspector.TimelineCalculator._minWidth;
1306 var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * this._workingArea + widthAdjustment;
1307 var cpuWidth = percentages.cpuWidth / 100 * this._workingArea + widthAdjustment;
1308 if (percentages.endWithChildren > percentages.end)
1309 widthWithChildren += widthAdjustment;
1310 return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth};
1313 setWindow: function(minimumBoundary, maximumBoundary)
1315 this._minimumBoundary = minimumBoundary;
1316 this._maximumBoundary = maximumBoundary;
1320 * @param {number} paddingLeft
1321 * @param {number} clientWidth
1323 setDisplayWindow: function(paddingLeft, clientWidth)
1325 this._workingArea = clientWidth - WebInspector.TimelineCalculator._minWidth - paddingLeft;
1326 this.paddingLeft = paddingLeft;
1330 * @param {number} value
1331 * @param {boolean=} hires
1334 formatTime: function(value, hires)
1336 return Number.secondsToString(value + this._minimumBoundary - this._model.minimumRecordTime(), hires);
1342 maximumBoundary: function()
1344 return this._maximumBoundary;
1350 minimumBoundary: function()
1352 return this._minimumBoundary;
1358 zeroTime: function()
1360 return this._model.minimumRecordTime();
1366 boundarySpan: function()
1368 return this._maximumBoundary - this._minimumBoundary;
1374 * @param {function(!WebInspector.TimelinePresentationModel.Record)} selectRecord
1375 * @param {function()} scheduleRefresh
1377 WebInspector.TimelineRecordListRow = function(selectRecord, scheduleRefresh)
1379 this.element = document.createElement("div");
1380 this.element.row = this;
1381 this.element.style.cursor = "pointer";
1382 this.element.addEventListener("click", this._onClick.bind(this), false);
1383 this.element.addEventListener("mouseover", this._onMouseOver.bind(this), false);
1384 this.element.addEventListener("mouseout", this._onMouseOut.bind(this), false);
1386 // Warning is float right block, it goes first.
1387 this._warningElement = this.element.createChild("div", "timeline-tree-item-warning hidden");
1389 this._expandArrowElement = this.element.createChild("div", "timeline-tree-item-expand-arrow");
1390 this._expandArrowElement.addEventListener("click", this._onExpandClick.bind(this), false);
1391 var iconElement = this.element.createChild("span", "timeline-tree-icon");
1392 this._typeElement = this.element.createChild("span", "type");
1394 this._dataElement = this.element.createChild("span", "data dimmed");
1395 this._scheduleRefresh = scheduleRefresh;
1396 this._selectRecord = selectRecord;
1399 WebInspector.TimelineRecordListRow.prototype = {
1400 update: function(record, offset)
1402 this._record = record;
1403 this._offset = offset;
1405 this.element.className = "timeline-tree-item timeline-category-" + record.category.name;
1406 var paddingLeft = 5;
1408 for (var currentRecord = record.parent ? record.parent.parent : null; currentRecord; currentRecord = currentRecord.parent)
1409 paddingLeft += 12 / (Math.max(1, step++));
1410 this.element.style.paddingLeft = paddingLeft + "px";
1411 if (record.isBackground())
1412 this.element.classList.add("background");
1414 this._typeElement.textContent = record.title;
1416 if (this._dataElement.firstChild)
1417 this._dataElement.removeChildren();
1419 this._warningElement.enableStyleClass("hidden", !record.hasWarnings() && !record.childHasWarnings());
1420 this._warningElement.enableStyleClass("timeline-tree-item-child-warning", record.childHasWarnings() && !record.hasWarnings());
1422 if (record.detailsNode())
1423 this._dataElement.appendChild(record.detailsNode());
1424 this._expandArrowElement.enableStyleClass("parent", record.children && record.children.length);
1425 this._expandArrowElement.enableStyleClass("expanded", record.visibleChildrenCount);
1426 this._record.setUserObject("WebInspector.TimelineRecordListRow", this);
1429 highlight: function(regExp, domChanges)
1431 var matchInfo = this.element.textContent.match(regExp);
1433 WebInspector.highlightSearchResult(this.element, matchInfo.index, matchInfo[0].length, domChanges);
1438 this.element.remove();
1442 * @param {!Event} event
1444 _onExpandClick: function(event)
1446 this._record.collapsed = !this._record.collapsed;
1447 this._record.clicked = true;
1448 this._scheduleRefresh();
1449 event.consume(true);
1453 * @param {?Event} event
1455 _onClick: function(event)
1457 this._selectRecord(this._record);
1461 * @param {boolean} selected
1463 renderAsSelected: function(selected)
1465 this.element.enableStyleClass("selected", selected);
1469 * @param {?Event} event
1471 _onMouseOver: function(event)
1473 this.element.classList.add("hovered");
1474 var graphRow = /** @type {!WebInspector.TimelineRecordGraphRow} */ (this._record.getUserObject("WebInspector.TimelineRecordGraphRow"));
1475 graphRow.element.classList.add("hovered");
1479 * @param {?Event} event
1481 _onMouseOut: function(event)
1483 this.element.classList.remove("hovered");
1484 var graphRow = /** @type {!WebInspector.TimelineRecordGraphRow} */ (this._record.getUserObject("WebInspector.TimelineRecordGraphRow"));
1485 graphRow.element.classList.remove("hovered");
1490 * @param {!WebInspector.TimelinePresentationModel.Record} record
1491 * @param {!RegExp} regExp
1493 WebInspector.TimelineRecordListRow.testContentMatching = function(record, regExp)
1495 var toSearchText = record.title;
1496 if (record.detailsNode())
1497 toSearchText += " " + record.detailsNode().textContent;
1498 return regExp.test(toSearchText);
1503 * @param {function(!WebInspector.TimelinePresentationModel.Record)} selectRecord
1504 * @param {function()} scheduleRefresh
1506 WebInspector.TimelineRecordGraphRow = function(graphContainer, selectRecord, scheduleRefresh)
1508 this.element = document.createElement("div");
1509 this.element.row = this;
1510 this.element.addEventListener("mouseover", this._onMouseOver.bind(this), false);
1511 this.element.addEventListener("mouseout", this._onMouseOut.bind(this), false);
1512 this.element.addEventListener("click", this._onClick.bind(this), false);
1514 this._barAreaElement = document.createElement("div");
1515 this._barAreaElement.className = "timeline-graph-bar-area";
1516 this.element.appendChild(this._barAreaElement);
1518 this._barWithChildrenElement = document.createElement("div");
1519 this._barWithChildrenElement.className = "timeline-graph-bar with-children";
1520 this._barWithChildrenElement.row = this;
1521 this._barAreaElement.appendChild(this._barWithChildrenElement);
1523 this._barCpuElement = document.createElement("div");
1524 this._barCpuElement.className = "timeline-graph-bar cpu"
1525 this._barCpuElement.row = this;
1526 this._barAreaElement.appendChild(this._barCpuElement);
1528 this._barElement = document.createElement("div");
1529 this._barElement.className = "timeline-graph-bar";
1530 this._barElement.row = this;
1531 this._barAreaElement.appendChild(this._barElement);
1533 this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer);
1535 this._selectRecord = selectRecord;
1536 this._scheduleRefresh = scheduleRefresh;
1539 WebInspector.TimelineRecordGraphRow.prototype = {
1540 update: function(record, calculator, expandOffset, index)
1542 this._record = record;
1543 this.element.className = "timeline-graph-side timeline-category-" + record.category.name;
1544 if (record.isBackground())
1545 this.element.classList.add("background");
1547 var barPosition = calculator.computeBarGraphWindowPosition(record);
1548 this._barWithChildrenElement.style.left = barPosition.left + "px";
1549 this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px";
1550 this._barElement.style.left = barPosition.left + "px";
1551 this._barElement.style.width = barPosition.width + "px";
1552 this._barCpuElement.style.left = barPosition.left + "px";
1553 this._barCpuElement.style.width = barPosition.cpuWidth + "px";
1554 this._expandElement._update(record, index, barPosition.left - expandOffset, barPosition.width);
1556 this._record.setUserObject("WebInspector.TimelineRecordGraphRow", this);
1560 * @param {?Event} event
1562 _onClick: function(event)
1564 // check if we click arrow and expand if yes.
1565 if (this._expandElement._arrow.containsEventPoint(event))
1567 this._selectRecord(this._record);
1571 * @param {boolean} selected
1573 renderAsSelected: function(selected)
1575 this.element.enableStyleClass("selected", selected);
1580 this._record.collapsed = !this._record.collapsed;
1581 this._record.clicked = true;
1582 this._scheduleRefresh();
1586 * @param {?Event} event
1588 _onMouseOver: function(event)
1590 this.element.classList.add("hovered");
1591 var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (this._record.getUserObject("WebInspector.TimelineRecordListRow"));
1592 listRow.element.classList.add("hovered");
1596 * @param {?Event} event
1598 _onMouseOut: function(event)
1600 this.element.classList.remove("hovered");
1601 var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (this._record.getUserObject("WebInspector.TimelineRecordListRow"));
1602 listRow.element.classList.remove("hovered");
1607 this.element.remove();
1608 this._expandElement._dispose();
1615 WebInspector.TimelineExpandableElement = function(container)
1617 this._element = container.createChild("div", "timeline-expandable");
1618 this._element.createChild("div", "timeline-expandable-left");
1619 this._arrow = this._element.createChild("div", "timeline-expandable-arrow");
1622 WebInspector.TimelineExpandableElement.prototype = {
1623 _update: function(record, index, left, width)
1625 const rowHeight = WebInspector.TimelinePanel.rowHeight;
1626 if (record.visibleChildrenCount || record.expandable) {
1627 this._element.style.top = index * rowHeight + "px";
1628 this._element.style.left = left + "px";
1629 this._element.style.width = Math.max(12, width + 25) + "px";
1630 if (!record.collapsed) {
1631 this._element.style.height = (record.visibleChildrenCount + 1) * rowHeight + "px";
1632 this._element.classList.add("timeline-expandable-expanded");
1633 this._element.classList.remove("timeline-expandable-collapsed");
1635 this._element.style.height = rowHeight + "px";
1636 this._element.classList.add("timeline-expandable-collapsed");
1637 this._element.classList.remove("timeline-expandable-expanded");
1639 this._element.classList.remove("hidden");
1641 this._element.classList.add("hidden");
1644 _dispose: function()
1646 this._element.remove();
1652 * @implements {WebInspector.TimelinePresentationModel.Filter}
1654 WebInspector.TimelineCategoryFilter = function()
1658 WebInspector.TimelineCategoryFilter.prototype = {
1660 * @param {!WebInspector.TimelinePresentationModel.Record} record
1663 accept: function(record)
1665 return !record.category.hidden;
1671 * @implements {WebInspector.TimelinePresentationModel.Filter}
1673 WebInspector.TimelineIsLongFilter = function()
1675 this._minimumRecordDuration = 0;
1678 WebInspector.TimelineIsLongFilter.prototype = {
1680 * @param {number} value
1682 setMinimumRecordDuration: function(value)
1684 this._minimumRecordDuration = value;
1688 * @param {!WebInspector.TimelinePresentationModel.Record} record
1691 accept: function(record)
1693 return this._minimumRecordDuration ? ((record.lastChildEndTime - record.startTime) >= this._minimumRecordDuration) : true;
1698 * @param {!RegExp} regExp
1700 * @implements {WebInspector.TimelinePresentationModel.Filter}
1702 WebInspector.TimelineSearchFilter = function(regExp)
1704 this._regExp = regExp;
1707 WebInspector.TimelineSearchFilter.prototype = {
1709 * @param {!WebInspector.TimelinePresentationModel.Record} record
1712 accept: function(record)
1714 return WebInspector.TimelineRecordListRow.testContentMatching(record, this._regExp);
1720 * @implements {WebInspector.TimelinePresentationModel.Filter}
1722 WebInspector.TimelineWindowFilter = function()
1727 WebInspector.TimelineWindowFilter.prototype = {
1730 this._windowStartTime = 0;
1731 this._windowEndTime = Infinity;
1734 setWindowTimes: function(windowStartTime, windowEndTime)
1736 this._windowStartTime = windowStartTime;
1737 this._windowEndTime = windowEndTime;
1741 * @param {!WebInspector.TimelinePresentationModel.Record} record
1744 accept: function(record)
1746 return record.lastChildEndTime >= this._windowStartTime && record.startTime <= this._windowEndTime;