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);
52 this._model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this);
54 // Create presentation model.
55 this._presentationModel = new WebInspector.TimelinePresentationModel();
56 this._durationFilter = new WebInspector.TimelineIsLongFilter();
57 this._windowFilter = new WebInspector.TimelineWindowFilter();
59 this._presentationModel.addFilter(this._windowFilter);
60 this._presentationModel.addFilter(new WebInspector.TimelineCategoryFilter());
61 this._presentationModel.addFilter(this._durationFilter);
63 this._frameMode = mode === WebInspector.TimelinePanel.Mode.Frames;
64 this._boundariesAreValid = true;
67 // Create layout componets.
69 // |-------------------------------|
73 // |----------------| |
75 // -------------------------------
77 // Create top level properties splitter.
78 this._detailsSplitView = new WebInspector.SplitView(false, "timeline-details");
79 this._detailsSplitView.element.classList.add("timeline-details-split");
80 this._detailsSplitView.sidebarElement().classList.add("timeline-details");
81 this._detailsSplitView.setMainElementConstraints(undefined, 40);
82 this._detailsView = new WebInspector.TimelineDetailsView();
83 this._detailsSplitView.setSidebarView(this._detailsView);
84 this._detailsSplitView.installResizer(this._detailsView.titleElement());
86 WebInspector.dockController.addEventListener(WebInspector.DockController.Events.DockSideChanged, this._dockSideChanged.bind(this));
87 WebInspector.settings.splitVerticallyWhenDockedToRight.addChangeListener(this._dockSideChanged.bind(this));
88 this._dockSideChanged();
90 this._searchableView = new WebInspector.SearchableView(this);
91 this._searchableView.element.classList.add("searchable-view");
92 this._detailsSplitView.setMainView(this._searchableView);
95 this._recordsView = this._createRecordsView();
96 this._views.push(this._recordsView);
98 this._stackView = new WebInspector.StackView(false);
99 this._stackView.show(this._searchableView.element);
100 this._stackView.element.classList.add("timeline-view-stack");
101 this._recordsViewMainElement = this._stackView.appendView(this._recordsView, "timeline-records").mainElement();
102 this._recordsViewMainElement.classList.add("timeline-records-view");
103 this._recordsViewMainElement.appendChild(this._timelineGrid.gridHeaderElement);
105 if (this._currentMode === WebInspector.TimelinePanel.Mode.Memory) {
106 // Create memory statistics as a bottom memory splitter child.
107 this._memoryStatistics = new WebInspector.CountersGraph(this, this._model);
108 this._views.push(this._memoryStatistics);
109 this._memoryStatistics.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._sidebarResized, this);
110 this._stackView.appendView(this._memoryStatistics, "timeline-memory");
113 this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
115 this.element.addEventListener("mousemove", this._mouseMove.bind(this), false);
116 this.element.addEventListener("mouseout", this._mouseOut.bind(this), false);
117 this.element.addEventListener("keydown", this._keyDown.bind(this), false);
119 this._expandOffset = 15;
121 this._windowStartTime = 0;
122 this._windowEndTime = Infinity;
124 this._allRecordsCount = 0;
126 this._presentationModel.setGlueRecords(glueRecordsSetting.get());
127 this._glueRecordsSetting = glueRecordsSetting;
128 this._glueRecordsSetting.addChangeListener(this._onGlueRecordsSettingChanged, this);
131 case WebInspector.TimelinePanel.Mode.Events:
132 this._overviewControl = new WebInspector.TimelineEventOverview(this._model);
134 case WebInspector.TimelinePanel.Mode.Frames:
135 this._overviewControl = new WebInspector.TimelineFrameOverview(this._model);
136 this._presentationModel.setGlueRecords(false);
137 this._frameController = new WebInspector.TimelineFrameController(this._model, this._overviewControl, this._presentationModel);
139 case WebInspector.TimelinePanel.Mode.Memory:
140 this._overviewControl = new WebInspector.TimelineMemoryOverview(this._model);
144 this._detailsSplitView.show(this.element);
147 WebInspector.TimelineView.commonUIFilters = function()
149 var filters = WebInspector.TimelineView._commonUIFilters;
154 filters._textFilterUI = new WebInspector.TextFilterUI();
156 var durationOptions = [];
157 for (var presetIndex = 0; presetIndex < WebInspector.TimelinePanel.durationFilterPresetsMs.length; ++presetIndex) {
158 var durationMs = WebInspector.TimelinePanel.durationFilterPresetsMs[presetIndex];
159 var durationOption = {};
161 durationOption.label = WebInspector.UIString("All");
162 durationOption.title = WebInspector.UIString("Show all records");
164 durationOption.label = WebInspector.UIString("\u2265 %dms", durationMs);
165 durationOption.title = WebInspector.UIString("Hide records shorter than %dms", durationMs);
167 durationOption.value = durationMs;
168 durationOptions.push(durationOption);
170 filters._durationFilterUI = new WebInspector.ComboBoxFilterUI(durationOptions);
172 filters._categoryFiltersUI = {};
173 var categoryTypes = [];
174 var categories = WebInspector.TimelinePresentationModel.categories();
175 for (var categoryName in categories) {
176 var category = categories[categoryName];
177 if (category.overviewStripGroupIndex < 0)
179 var filter = new WebInspector.CheckboxFilterUI(category.name, category.title);
180 filters._categoryFiltersUI[category.name] = filter;
182 WebInspector.TimelineView._commonUIFilters = filters;
186 WebInspector.TimelineView.prototype = {
188 * @return {!WebInspector.SidebarView}
190 _createRecordsView: function()
192 // Create records sidebar as a top memory splitter child.
193 var recordsView = new WebInspector.SidebarView(WebInspector.SidebarView.SidebarPosition.Start, "timeline-split");
194 recordsView.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._sidebarResized, this);
195 recordsView.setSecondIsSidebar(false);
196 this._containerElement = recordsView.element;
197 this._containerElement.tabIndex = 0;
198 this._containerElement.id = "timeline-container";
199 this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false);
201 // Create records list in the records sidebar.
202 recordsView.sidebarElement().createChild("div", "timeline-records-title").textContent = WebInspector.UIString("RECORDS");
203 this._sidebarListElement = recordsView.sidebarElement().createChild("div", "timeline-records-list");
205 // Create grid in the records main area.
206 this._gridContainer = new WebInspector.ViewWithResizeCallback(this._onViewportResize.bind(this));
207 this._gridContainer.element.id = "resources-container-content";
208 recordsView.setMainView(this._gridContainer);
209 this._timelineGrid = new WebInspector.TimelineGrid();
210 this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
211 this._itemsGraphsElement.id = "timeline-graphs";
212 this._gridContainer.element.appendChild(this._timelineGrid.element);
213 this._timelineGrid.gridHeaderElement.id = "timeline-grid-header";
214 this._timelineGrid.gridHeaderElement.classList.add("fill");
216 // Create gap elements
217 this._topGapElement = this._itemsGraphsElement.createChild("div", "timeline-gap");
218 this._graphRowsElement = this._itemsGraphsElement.createChild("div");
219 this._bottomGapElement = this._itemsGraphsElement.createChild("div", "timeline-gap");
220 this._expandElements = this._itemsGraphsElement.createChild("div");
221 this._expandElements.id = "orphan-expand-elements";
223 // Create gpu tasks containers.
224 /** @type {!Array.<!TimelineAgent.TimelineEvent>} */
225 this._mainThreadTasks = ([]);
226 /** @type {!Array.<!TimelineAgent.TimelineEvent>} */
227 this._gpuTasks = ([]);
228 var utilizationStripsElement = this._timelineGrid.gridHeaderElement.createChild("div", "timeline-utilization-strips vbox");
229 this._cpuBarsElement = utilizationStripsElement.createChild("div", "timeline-utilization-strip");
230 if (WebInspector.experimentsSettings.gpuTimeline.isEnabled())
231 this._gpuBarsElement = utilizationStripsElement.createChild("div", "timeline-utilization-strip gpu");
237 * @return {!WebInspector.SearchableView}
239 searchableView: function()
241 return this._searchableView;
247 supportsGlueParentMode: function()
249 return !this._frameMode;
252 _onGlueRecordsSettingChanged: function()
254 this._presentationModel.setGlueRecords(this._glueRecordsSetting.get());
255 this._repopulateRecords();
261 windowStartTime: function()
263 return this._windowStartTime || this._model.minimumRecordTime();
269 windowEndTime: function()
271 return this._windowEndTime < Infinity ? this._windowEndTime : this._model.maximumRecordTime();
275 * @return {!WebInspector.TimelineOverviewBase}
277 overviewControl: function()
279 return this._overviewControl;
284 return this._calculator;
288 * @param {!WebInspector.FilterBar} filterBar
291 createUIFilters: function(filterBar)
293 var filters = this._filters;
295 this._filters = WebInspector.TimelineView.commonUIFilters();
296 filters = this._filters;
298 filters._textFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._textFilterChanged, this);
299 filters._durationFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._durationFilterChanged, this);
300 for (var categoryName in filters._categoryFiltersUI)
301 filters._categoryFiltersUI[categoryName].addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._categoriesFilterChanged.bind(this, categoryName), this);
304 filterBar.addFilter(filters._textFilterUI);
305 filterBar.addFilter(filters._durationFilterUI);
306 for (var categoryName in filters._categoryFiltersUI)
307 filterBar.addFilter(filters._categoryFiltersUI[categoryName]);
312 _textFilterChanged: function(event)
314 var searchQuery = this._filters._textFilterUI.value();
315 this._presentationModel.setSearchFilter(null);
316 delete this._searchFilter;
318 function cleanRecord(record)
320 delete record.clicked;
322 WebInspector.TimelinePresentationModel.forAllRecords(this._presentationModel.rootRecord().children, cleanRecord);
324 this.searchCanceled();
326 this._searchFilter = new WebInspector.TimelineSearchFilter(createPlainTextSearchRegex(searchQuery, "i"));
327 this._presentationModel.setSearchFilter(this._searchFilter);
329 this._invalidateAndScheduleRefresh(true, true);
332 _durationFilterChanged: function()
334 var duration = this._filters._durationFilterUI.value();
335 var minimumRecordDuration = +duration / 1000.0;
336 this._durationFilter.setMinimumRecordDuration(minimumRecordDuration);
337 this._invalidateAndScheduleRefresh(true, true);
340 _categoriesFilterChanged: function(name, event)
342 var categories = WebInspector.TimelinePresentationModel.categories();
343 categories[name].hidden = !this._filters._categoryFiltersUI[name].checked();
344 this._invalidateAndScheduleRefresh(true, true);
347 _dockSideChanged: function()
349 var dockSide = WebInspector.dockController.dockSide();
350 var vertically = false;
351 if (dockSide === WebInspector.DockController.State.DockedToBottom)
354 vertically = !WebInspector.settings.splitVerticallyWhenDockedToRight.get();
355 this._detailsSplitView.setVertical(vertically);
356 this._detailsView.setVertical(vertically);
359 _rootRecord: function()
361 return this._presentationModel.rootRecord();
364 _updateRecordsCounter: function(recordsInWindowCount)
366 this._panel.recordsCounter.setText(WebInspector.UIString("%d of %d records shown", recordsInWindowCount, this._allRecordsCount));
369 _updateFrameStatistics: function(frames)
371 this._lastFrameStatistics = frames.length ? new WebInspector.FrameStatistics(frames) : null;
374 _updateEventDividers: function()
376 this._timelineGrid.removeEventDividers();
377 var clientWidth = this._graphRowsElementWidth;
379 var eventDividerRecords = this._presentationModel.eventDividerRecords();
381 for (var i = 0; i < eventDividerRecords.length; ++i) {
382 var record = eventDividerRecords[i];
383 var positions = this._calculator.computeBarGraphWindowPosition(record);
384 var dividerPosition = Math.round(positions.left);
385 if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition])
387 var divider = WebInspector.TimelinePresentationModel.createEventDivider(record.type, record.title);
388 divider.style.left = dividerPosition + "px";
389 dividers[dividerPosition] = divider;
391 this._timelineGrid.addEventDividers(dividers);
394 _updateFrameBars: function(frames)
396 var clientWidth = this._graphRowsElementWidth;
397 if (this._frameContainer)
398 this._frameContainer.removeChildren();
400 const frameContainerBorderWidth = 1;
401 this._frameContainer = document.createElement("div");
402 this._frameContainer.classList.add("fill");
403 this._frameContainer.classList.add("timeline-frame-container");
404 this._frameContainer.style.height = WebInspector.TimelinePanel.rowHeight + frameContainerBorderWidth + "px";
405 this._frameContainer.addEventListener("dblclick", this._onFrameDoubleClicked.bind(this), false);
410 for (var i = 0; i < frames.length; ++i) {
411 var frame = frames[i];
412 var frameStart = this._calculator.computePosition(frame.startTime);
413 var frameEnd = this._calculator.computePosition(frame.endTime);
415 var frameStrip = document.createElement("div");
416 frameStrip.className = "timeline-frame-strip";
417 var actualStart = Math.max(frameStart, 0);
418 var width = frameEnd - actualStart;
419 frameStrip.style.left = actualStart + "px";
420 frameStrip.style.width = width + "px";
421 frameStrip._frame = frame;
423 const minWidthForFrameInfo = 60;
424 if (width > minWidthForFrameInfo)
425 frameStrip.textContent = Number.secondsToString(frame.endTime - frame.startTime, true);
427 this._frameContainer.appendChild(frameStrip);
429 if (actualStart > 0) {
430 var frameMarker = WebInspector.TimelinePresentationModel.createEventDivider(WebInspector.TimelineModel.RecordType.BeginFrame);
431 frameMarker.style.left = frameStart + "px";
432 dividers.push(frameMarker);
435 this._timelineGrid.addEventDividers(dividers);
436 this._timelineGrid.gridHeaderElement.appendChild(this._frameContainer);
439 _onFrameDoubleClicked: function(event)
441 var frameBar = event.target.enclosingNodeOrSelfWithClass("timeline-frame-strip");
444 this._setWindowTimes(frameBar._frame.startTime, frameBar._frame.endTime);
447 _updateWindowBoundaries: function()
449 var windowBoundaries = this.overviewControl().windowBoundaries(this._windowStartTime, this._windowEndTime);
450 this._panel.setWindow(windowBoundaries.left, windowBoundaries.right);
454 * @return {!{windowStartTime: number, windowEndTime: number}}
456 windowTimes: function()
458 return {windowStartTime: this._windowStartTime, windowEndTime: this._windowEndTime};
462 * @param {?Object} windowTimes
464 setWindowTimes: function(windowTimes)
468 this._setWindowTimes(windowTimes.windowStartTime, windowTimes.windowEndTime);
472 * @param {number} startTime
473 * @param {number} endTime
475 _setWindowTimes: function(startTime, endTime)
477 this._windowStartTime = startTime;
478 this._windowEndTime = endTime;
479 this._windowFilter.setWindowTimes(startTime, endTime);
480 var windowBoundaries = this.overviewControl().windowBoundaries(startTime, endTime);
481 this._panel.setWindow(windowBoundaries.left, windowBoundaries.right);
484 _repopulateRecords: function()
487 this._automaticallySizeWindow = false;
488 var records = this._model.records;
489 for (var i = 0; i < records.length; ++i)
490 this._innerAddRecordToTimeline(records[i]);
491 this._invalidateAndScheduleRefresh(false, false);
494 _onTimelineEventRecorded: function(event)
496 if (this._innerAddRecordToTimeline(/** @type {!TimelineAgent.TimelineEvent} */(event.data)))
497 this._invalidateAndScheduleRefresh(false, false);
501 * @param {!TimelineAgent.TimelineEvent} record
504 _innerAddRecordToTimeline: function(record)
506 if (record.type === WebInspector.TimelineModel.RecordType.Program)
507 this._mainThreadTasks.push(record);
509 if (record.type === WebInspector.TimelineModel.RecordType.GPUTask) {
510 this._gpuTasks.push(record);
511 return WebInspector.TimelineModel.startTimeInSeconds(record) < this._windowEndTime;
514 var records = this._presentationModel.addRecord(record);
515 this._allRecordsCount += records.length;
516 var hasVisibleRecords = false;
517 var presentationModel = this._presentationModel;
518 function checkVisible(record)
520 hasVisibleRecords |= presentationModel.isVisible(record);
522 WebInspector.TimelinePresentationModel.forAllRecords(records, checkVisible);
524 function isAdoptedRecord(record)
526 return record.parent !== presentationModel.rootRecord;
528 // Tell caller update is necessary either if we added a visible record or if we re-parented a record.
529 return hasVisibleRecords || records.some(isAdoptedRecord);
533 * @param {!WebInspector.Event} event
535 _sidebarResized: function(event)
537 var width = /** @type {number} */(event.data);
538 this.setSidebarWidth(width);
539 this._panel.setSidebarWidth(width);
543 * @param {number} width
545 setSidebarWidth: function(width)
547 this._timelineGrid.gridHeaderElement.style.left = width + "px";
548 for (var i = 0; i < this._views.length; ++i)
549 this._views[i].setSidebarWidth(width);
552 _onViewportResize: function()
554 this._resize(this._recordsView.sidebarWidth());
558 * @param {number} sidebarWidth
560 _resize: function(sidebarWidth)
562 this._closeRecordDetails();
563 this._graphRowsElementWidth = this._graphRowsElement.offsetWidth;
564 this._containerElementHeight = this._containerElement.clientHeight;
565 this._timelineGrid.gridHeaderElement.style.width = this._itemsGraphsElement.offsetWidth + "px";
566 this._scheduleRefresh(false, true);
569 _resetView: function()
571 this._presentationModel.reset();
572 this._boundariesAreValid = false;
573 this._adjustScrollPosition(0);
574 this._closeRecordDetails();
575 this._allRecordsCount = 0;
576 this._automaticallySizeWindow = true;
577 this._mainThreadTasks = [];
581 _onRecordsCleared: function()
583 this._windowStartTime = 0;
584 this._windowEndTime = Infinity;
587 this.overviewControl().reset();
588 this._windowFilter.reset();
589 this._invalidateAndScheduleRefresh(true, true);
593 * @return {!Array.<!Element>}
595 elementsToRestoreScrollPositionsFor: function()
597 return [this._containerElement];
602 WebInspector.View.prototype.wasShown.call(this);
604 this._repopulateRecords();
605 this._updateSelectionDetails();
606 this._updateWindowBoundaries();
608 if (!WebInspector.TimelinePanel._categoryStylesInitialized) {
609 WebInspector.TimelinePanel._categoryStylesInitialized = true;
610 this._injectCategoryStyles();
612 this._onViewportResize();
618 this._closeRecordDetails();
619 WebInspector.View.prototype.willHide.call(this);
622 _onScroll: function(event)
624 this._closeRecordDetails();
625 this._scrollTop = this._containerElement.scrollTop;
626 var dividersTop = Math.max(0, this._scrollTop);
627 this._timelineGrid.setScrollAndDividerTop(this._scrollTop, dividersTop);
628 this._scheduleRefresh(true, true);
632 * @param {boolean} preserveBoundaries
633 * @param {boolean} userGesture
635 _invalidateAndScheduleRefresh: function(preserveBoundaries, userGesture)
637 this._presentationModel.invalidateFilteredRecords();
638 delete this._searchResults;
639 this._scheduleRefresh(preserveBoundaries, userGesture);
643 * @param {?WebInspector.TimelinePresentationModel.Record} record
645 _selectRecord: function(record)
647 if (record === this._lastSelectedRecord)
650 // Remove selection rendering.
651 if (this._lastSelectedRecord) {
652 var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (this._lastSelectedRecord.getUserObject("WebInspector.TimelineRecordListRow"));
654 listRow.renderAsSelected(false);
655 var graphRow = /** @type {!WebInspector.TimelineRecordGraphRow} */ (this._lastSelectedRecord.getUserObject("WebInspector.TimelineRecordGraphRow"));
657 graphRow.renderAsSelected(false);
661 this._updateSelectionDetails();
665 this._lastSelectedRecord = record;
666 this._revealRecord(record);
667 var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (record.getUserObject("WebInspector.TimelineRecordListRow"));
669 listRow.renderAsSelected(true);
670 var graphRow = /** @type {!WebInspector.TimelineRecordListRow} */ (record.getUserObject("WebInspector.TimelineRecordGraphRow"));
672 graphRow.renderAsSelected(true);
674 record.generatePopupContent(showCallback.bind(this));
677 * @param {!DocumentFragment} element
678 * @this {WebInspector.TimelineView}
680 function showCallback(element)
682 this._detailsView.setContent(record.title, element);
686 _updateSelectionDetails: function()
688 var startTime = this.windowStartTime() * 1000;
689 var endTime = this.windowEndTime() * 1000;
690 // Return early in case 0 selection window.
694 var aggregatedStats = {};
697 * @param {number} value
698 * @param {!TimelineAgent.TimelineEvent} task
701 function compareEndTime(value, task)
703 return value < task.endTime ? -1 : 1;
707 * @param {!TimelineAgent.TimelineEvent} rawRecord
709 function aggregateTimeForRecordWithinWindow(rawRecord)
711 if (!rawRecord.endTime || rawRecord.endTime < startTime || rawRecord.startTime > endTime)
714 var childrenTime = 0;
715 var children = rawRecord.children || [];
716 for (var i = 0; i < children.length; ++i) {
717 var child = children[i];
718 if (!child.endTime || child.endTime < startTime || child.startTime > endTime)
720 childrenTime += Math.min(endTime, child.endTime) - Math.max(startTime, child.startTime);
721 aggregateTimeForRecordWithinWindow(child);
723 var categoryName = WebInspector.TimelinePresentationModel.categoryForRecord(rawRecord).name;
724 var ownTime = Math.min(endTime, rawRecord.endTime) - Math.max(startTime, rawRecord.startTime) - childrenTime;
725 aggregatedStats[categoryName] = (aggregatedStats[categoryName] || 0) + ownTime / 1000;
728 var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, this._mainThreadTasks, compareEndTime);
729 for (; taskIndex < this._mainThreadTasks.length; ++taskIndex) {
730 var task = this._mainThreadTasks[taskIndex];
731 if (task.startTime > endTime)
733 aggregateTimeForRecordWithinWindow(task);
736 var aggregatedTotal = 0;
737 for (var categoryName in aggregatedStats)
738 aggregatedTotal += aggregatedStats[categoryName];
739 aggregatedStats["idle"] = Math.max(0, (endTime - startTime) / 1000 - aggregatedTotal);
741 var fragment = document.createDocumentFragment();
742 var pie = WebInspector.TimelinePresentationModel.generatePieChart(aggregatedStats);
743 fragment.appendChild(pie.element);
745 if (this._frameMode && this._lastFrameStatistics) {
746 var title = WebInspector.UIString("%s \u2013 %s (%d frames)", Number.secondsToString(this._lastFrameStatistics.startOffset, true), Number.secondsToString(this._lastFrameStatistics.endOffset, true), this._lastFrameStatistics.frameCount);
747 fragment.appendChild(WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics(this._lastFrameStatistics));
749 var title = WebInspector.UIString("%s \u2013 %s", this._calculator.formatTime(0, true), this._calculator.formatTime(this._calculator.boundarySpan(), true));
751 this._detailsView.setContent(title, fragment);
755 * @param {number} left
756 * @param {number} right
758 windowChanged: function(left, right)
760 var windowTimes = this.overviewControl().windowTimes(left, right);
761 this._windowStartTime = windowTimes.startTime;
762 this._windowEndTime = windowTimes.endTime;
763 this._windowFilter.setWindowTimes(windowTimes.startTime, windowTimes.endTime);
764 this._invalidateAndScheduleRefresh(false, true);
765 this._selectRecord(null);
769 * @param {boolean} preserveBoundaries
770 * @param {boolean} userGesture
772 _scheduleRefresh: function(preserveBoundaries, userGesture)
774 this._closeRecordDetails();
775 this._boundariesAreValid &= preserveBoundaries;
777 if (!this.isShowing())
780 if (preserveBoundaries || userGesture)
783 if (!this._refreshTimeout)
784 this._refreshTimeout = setTimeout(this._refresh.bind(this), 300);
790 if (this._refreshTimeout) {
791 clearTimeout(this._refreshTimeout);
792 delete this._refreshTimeout;
795 this._timelinePaddingLeft = this._expandOffset;
796 this._calculator.setWindow(this.windowStartTime(), this.windowEndTime());
797 this._calculator.setDisplayWindow(this._timelinePaddingLeft, this._graphRowsElementWidth);
799 var recordsInWindowCount = this._refreshRecords();
800 this._updateRecordsCounter(recordsInWindowCount);
801 if (!this._boundariesAreValid) {
802 this._updateEventDividers();
803 var frames = this._frameController && this._presentationModel.filteredFrames(this.windowStartTime(), this.windowEndTime());
805 this._updateFrameStatistics(frames);
806 const maxFramesForFrameBars = 30;
807 if (frames.length && frames.length < maxFramesForFrameBars) {
808 this._timelineGrid.removeDividers();
809 this._updateFrameBars(frames);
811 if (this._frameContainer)
812 this._frameContainer.remove();
813 this._timelineGrid.updateDividers(this._calculator);
816 this._timelineGrid.updateDividers(this._calculator);
817 this._refreshAllUtilizationBars();
819 if (this._currentMode === WebInspector.TimelinePanel.Mode.Memory)
820 this._memoryStatistics.refresh();
821 this._updateWindowBoundaries();
822 this._boundariesAreValid = true;
825 revealRecordAt: function(time)
828 function findRecordToReveal(record)
830 if (record.containsTime(time)) {
831 recordToReveal = record;
834 // If there is no record containing the time than use the latest one before that time.
835 if (!recordToReveal || record.endTime < time && recordToReveal.endTime < record.endTime)
836 recordToReveal = record;
839 WebInspector.TimelinePresentationModel.forAllRecords(this._presentationModel.rootRecord().children, null, findRecordToReveal);
841 // The record ends before the window left bound so scroll to the top.
842 if (!recordToReveal) {
843 this._containerElement.scrollTop = 0;
847 this._selectRecord(recordToReveal);
851 * @param {!WebInspector.TimelinePresentationModel.Record} recordToReveal
853 _revealRecord: function(recordToReveal)
855 var needRefresh = false;
856 // Expand all ancestors.
857 for (var parent = recordToReveal.parent; parent !== this._rootRecord(); parent = parent.parent) {
858 if (!parent.collapsed)
860 this._presentationModel.invalidateFilteredRecords();
861 parent.collapsed = false;
864 var recordsInWindow = this._presentationModel.filteredRecords();
865 var index = recordsInWindow.indexOf(recordToReveal);
867 var itemOffset = index * WebInspector.TimelinePanel.rowHeight;
868 var visibleTop = this._scrollTop - WebInspector.TimelinePanel.headerHeight;
869 var visibleBottom = visibleTop + this._containerElementHeight - WebInspector.TimelinePanel.rowHeight;
870 if (itemOffset < visibleTop)
871 this._containerElement.scrollTop = itemOffset;
872 else if (itemOffset > visibleBottom)
873 this._containerElement.scrollTop = itemOffset - this._containerElementHeight + WebInspector.TimelinePanel.headerHeight + WebInspector.TimelinePanel.rowHeight;
874 else if (needRefresh)
875 this._refreshRecords();
878 _refreshRecords: function()
880 var recordsInWindow = this._presentationModel.filteredRecords();
882 // Calculate the visible area.
883 var visibleTop = this._scrollTop;
884 var visibleBottom = visibleTop + this._containerElementHeight;
886 var rowHeight = WebInspector.TimelinePanel.rowHeight;
887 var headerHeight = WebInspector.TimelinePanel.headerHeight;
889 // Convert visible area to visible indexes. Always include top-level record for a visible nested record.
890 var startIndex = Math.max(0, Math.min(Math.floor((visibleTop - headerHeight) / rowHeight), recordsInWindow.length - 1));
891 var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));
892 var lastVisibleLine = Math.max(0, Math.floor((visibleBottom - headerHeight) / rowHeight));
893 if (this._automaticallySizeWindow && recordsInWindow.length > lastVisibleLine) {
894 this._automaticallySizeWindow = false;
895 this._selectRecord(null);
896 // If we're at the top, always use real timeline start as a left window bound so that expansion arrow padding logic works.
897 var windowStartTime = startIndex ? recordsInWindow[startIndex].startTime : this._model.minimumRecordTime();
898 this._setWindowTimes(windowStartTime, recordsInWindow[Math.max(0, lastVisibleLine - 1)].endTime);
899 recordsInWindow = this._presentationModel.filteredRecords();
900 endIndex = Math.min(recordsInWindow.length, lastVisibleLine);
902 this._updateWindowBoundaries();
905 // Resize gaps first.
906 this._topGapElement.style.height = (startIndex * rowHeight) + "px";
907 this._recordsView.sidebarElement().firstElementChild.style.flexBasis = (startIndex * rowHeight + headerHeight) + "px";
908 this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px";
909 var rowsHeight = headerHeight + recordsInWindow.length * rowHeight;
910 var totalHeight = Math.max(this._containerElementHeight, rowsHeight);
912 this._recordsView.firstElement().style.height = totalHeight + "px";
913 this._recordsView.secondElement().style.height = totalHeight + "px";
914 this._recordsView.resizerElement().style.height = totalHeight + "px";
916 // Update visible rows.
917 var listRowElement = this._sidebarListElement.firstChild;
918 var width = this._graphRowsElementWidth;
919 this._itemsGraphsElement.removeChild(this._graphRowsElement);
920 var graphRowElement = this._graphRowsElement.firstChild;
921 var scheduleRefreshCallback = this._invalidateAndScheduleRefresh.bind(this, true, true);
922 var selectRecordCallback = this._selectRecord.bind(this);
923 this._itemsGraphsElement.removeChild(this._expandElements);
924 this._expandElements.removeChildren();
926 for (var i = 0; i < endIndex; ++i) {
927 var record = recordsInWindow[i];
929 if (i < startIndex) {
930 var lastChildIndex = i + record.visibleChildrenCount;
931 if (lastChildIndex >= startIndex && lastChildIndex < endIndex) {
932 var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements);
933 var positions = this._calculator.computeBarGraphWindowPosition(record);
934 expandElement._update(record, i, positions.left - this._expandOffset, positions.width);
937 if (!listRowElement) {
938 listRowElement = new WebInspector.TimelineRecordListRow(selectRecordCallback, scheduleRefreshCallback).element;
939 this._sidebarListElement.appendChild(listRowElement);
941 if (!graphRowElement) {
942 graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, selectRecordCallback, scheduleRefreshCallback).element;
943 this._graphRowsElement.appendChild(graphRowElement);
946 listRowElement.row.update(record, visibleTop);
947 graphRowElement.row.update(record, this._calculator, this._expandOffset, i);
948 if (this._lastSelectedRecord === record) {
949 listRowElement.row.renderAsSelected(true);
950 graphRowElement.row.renderAsSelected(true);
953 listRowElement = listRowElement.nextSibling;
954 graphRowElement = graphRowElement.nextSibling;
958 // Remove extra rows.
959 while (listRowElement) {
960 var nextElement = listRowElement.nextSibling;
961 listRowElement.row.dispose();
962 listRowElement = nextElement;
964 while (graphRowElement) {
965 var nextElement = graphRowElement.nextSibling;
966 graphRowElement.row.dispose();
967 graphRowElement = nextElement;
970 this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement);
971 this._itemsGraphsElement.appendChild(this._expandElements);
972 this._adjustScrollPosition(recordsInWindow.length * rowHeight + headerHeight);
973 this._updateSearchHighlight(false, true);
975 return recordsInWindow.length;
978 _refreshAllUtilizationBars: function()
980 this._refreshUtilizationBars(WebInspector.UIString("CPU"), this._mainThreadTasks, this._cpuBarsElement);
981 if (WebInspector.experimentsSettings.gpuTimeline.isEnabled())
982 this._refreshUtilizationBars(WebInspector.UIString("GPU"), this._gpuTasks, this._gpuBarsElement);
986 * @param {string} name
987 * @param {!Array.<!TimelineAgent.TimelineEvent>} tasks
988 * @param {?Element} container
990 _refreshUtilizationBars: function(name, tasks, container)
998 var minWidth = WebInspector.TimelineCalculator._minWidth;
999 var widthAdjustment = minWidth / 2;
1001 var width = this._graphRowsElementWidth;
1002 var boundarySpan = this.windowEndTime() - this.windowStartTime();
1003 var scale = boundarySpan / (width - minWidth - this._timelinePaddingLeft);
1004 var startTime = (this.windowStartTime() - this._timelinePaddingLeft * scale) * 1000;
1005 var endTime = startTime + width * scale * 1000;
1008 * @param {number} value
1009 * @param {!TimelineAgent.TimelineEvent} task
1012 function compareEndTime(value, task)
1014 return value < task.endTime ? -1 : 1;
1017 var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, tasks, compareEndTime);
1019 var foreignStyle = "gpu-task-foreign";
1020 var element = container.firstChild;
1025 for (; taskIndex < tasks.length; ++taskIndex) {
1026 var task = tasks[taskIndex];
1027 if (task.startTime > endTime)
1030 var left = Math.max(0, this._calculator.computePosition(WebInspector.TimelineModel.startTimeInSeconds(task)) + barOffset - widthAdjustment);
1031 var right = Math.min(width, this._calculator.computePosition(WebInspector.TimelineModel.endTimeInSeconds(task)) + barOffset + widthAdjustment);
1034 var gap = Math.floor(left) - Math.ceil(lastRight);
1036 if (!task.data["foreign"])
1037 lastElement.classList.remove(foreignStyle);
1039 lastElement._tasksInfo.lastTaskIndex = taskIndex;
1042 lastElement.style.width = (lastRight - lastLeft) + "px";
1046 element = container.createChild("div", "timeline-graph-bar");
1047 element.style.left = left + "px";
1048 element._tasksInfo = {name: name, tasks: tasks, firstTaskIndex: taskIndex, lastTaskIndex: taskIndex};
1049 if (task.data["foreign"])
1050 element.classList.add(foreignStyle);
1053 lastElement = element;
1054 element = element.nextSibling;
1058 lastElement.style.width = (lastRight - lastLeft) + "px";
1061 var nextElement = element.nextSibling;
1062 element._tasksInfo = null;
1063 container.removeChild(element);
1064 element = nextElement;
1068 _adjustScrollPosition: function(totalHeight)
1070 // Prevent the container from being scrolled off the end.
1071 if ((this._scrollTop + this._containerElementHeight) > totalHeight + 1)
1072 this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight);
1075 _getPopoverAnchor: function(element)
1077 var anchor = element.enclosingNodeOrSelfWithClass("timeline-graph-bar");
1078 if (anchor && anchor._tasksInfo)
1080 return element.enclosingNodeOrSelfWithClass("timeline-frame-strip");
1083 _mouseOut: function()
1085 this._hideQuadHighlight();
1091 _mouseMove: function(e)
1093 var rowElement = e.target.enclosingNodeOrSelfWithClass("timeline-tree-item");
1094 if (rowElement && rowElement.row && rowElement.row._record.highlightQuad)
1095 this._highlightQuad(rowElement.row._record.highlightQuad);
1097 this._hideQuadHighlight();
1099 var taskBarElement = e.target.enclosingNodeOrSelfWithClass("timeline-graph-bar");
1100 if (taskBarElement && taskBarElement._tasksInfo) {
1101 var offset = taskBarElement.offsetLeft;
1102 this._timelineGrid.showCurtains(offset >= 0 ? offset : 0, taskBarElement.offsetWidth);
1104 this._timelineGrid.hideCurtains();
1108 * @param {?Event} event
1110 _keyDown: function(event)
1112 if (!this._lastSelectedRecord || event.shiftKey || event.metaKey || event.ctrlKey)
1115 var record = this._lastSelectedRecord;
1116 var recordsInWindow = this._presentationModel.filteredRecords();
1117 var index = recordsInWindow.indexOf(record);
1118 var recordsInPage = Math.floor(this._containerElementHeight / WebInspector.TimelinePanel.rowHeight);
1119 var rowHeight = WebInspector.TimelinePanel.rowHeight;
1124 switch (event.keyIdentifier) {
1126 if (record.parent) {
1127 if ((!record.expandable || record.collapsed) && record.parent !== this._presentationModel.rootRecord()) {
1128 this._selectRecord(record.parent);
1130 record.collapsed = true;
1131 record.clicked = true;
1132 this._invalidateAndScheduleRefresh(true, true);
1135 event.consume(true);
1140 this._selectRecord(recordsInWindow[index]);
1141 event.consume(true);
1144 if (record.expandable && record.collapsed) {
1145 record.collapsed = false;
1146 record.clicked = true;
1147 this._invalidateAndScheduleRefresh(true, true);
1149 if (++index >= recordsInWindow.length)
1151 this._selectRecord(recordsInWindow[index]);
1153 event.consume(true);
1156 if (++index >= recordsInWindow.length)
1158 this._selectRecord(recordsInWindow[index]);
1159 event.consume(true);
1162 index = Math.max(0, index - recordsInPage);
1163 this._scrollTop = Math.max(0, this._scrollTop - recordsInPage * rowHeight);
1164 this._containerElement.scrollTop = this._scrollTop;
1165 this._selectRecord(recordsInWindow[index]);
1166 event.consume(true);
1169 index = Math.min(recordsInWindow.length - 1, index + recordsInPage);
1170 this._scrollTop = Math.min(this._containerElement.scrollHeight - this._containerElementHeight, this._scrollTop + recordsInPage * rowHeight);
1171 this._containerElement.scrollTop = this._scrollTop;
1172 this._selectRecord(recordsInWindow[index]);
1173 event.consume(true);
1177 this._selectRecord(recordsInWindow[index]);
1178 event.consume(true);
1181 index = recordsInWindow.length - 1;
1182 this._selectRecord(recordsInWindow[index]);
1183 event.consume(true);
1189 * @param {!Array.<number>} quad
1191 _highlightQuad: function(quad)
1193 if (this._highlightedQuad === quad)
1195 this._highlightedQuad = quad;
1196 DOMAgent.highlightQuad(quad, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA());
1199 _hideQuadHighlight: function()
1201 if (this._highlightedQuad) {
1202 delete this._highlightedQuad;
1203 DOMAgent.hideHighlight();
1208 * @param {!Element} anchor
1209 * @param {!WebInspector.Popover} popover
1211 _showPopover: function(anchor, popover)
1213 if (anchor.classList.contains("timeline-frame-strip")) {
1214 var frame = anchor._frame;
1215 popover.show(WebInspector.TimelinePresentationModel.generatePopupContentForFrame(frame), anchor);
1217 if (anchor.row && anchor.row._record)
1218 anchor.row._record.generatePopupContent(showCallback);
1219 else if (anchor._tasksInfo)
1220 popover.show(this._presentationModel.generateMainThreadBarPopupContent(anchor._tasksInfo), anchor, null, null, WebInspector.Popover.Orientation.Bottom);
1223 function showCallback(popupContent)
1225 popover.show(popupContent, anchor);
1229 _closeRecordDetails: function()
1231 this._popoverHelper.hidePopover();
1234 _injectCategoryStyles: function()
1236 var style = document.createElement("style");
1237 var categories = WebInspector.TimelinePresentationModel.categories();
1239 style.textContent = Object.values(categories).map(WebInspector.TimelinePresentationModel.createStyleRuleForCategory).join("\n");
1240 document.head.appendChild(style);
1243 jumpToNextSearchResult: function()
1245 if (!this._searchResults || !this._searchResults.length)
1247 var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : -1;
1248 this._jumpToSearchResult(index + 1);
1251 jumpToPreviousSearchResult: function()
1253 if (!this._searchResults || !this._searchResults.length)
1255 var index = this._selectedSearchResult ? this._searchResults.indexOf(this._selectedSearchResult) : 0;
1256 this._jumpToSearchResult(index - 1);
1259 _jumpToSearchResult: function(index)
1261 this._selectSearchResult((index + this._searchResults.length) % this._searchResults.length);
1262 this._highlightSelectedSearchResult(true);
1265 _selectSearchResult: function(index)
1267 this._selectedSearchResult = this._searchResults[index];
1268 this._searchableView.updateCurrentMatchIndex(index);
1272 * @param {boolean} selectRecord
1274 _highlightSelectedSearchResult: function(selectRecord)
1276 this._clearHighlight();
1277 if (this._searchFilter)
1280 var record = this._selectedSearchResult;
1285 this._selectRecord(record);
1287 for (var element = this._sidebarListElement.firstChild; element; element = element.nextSibling) {
1288 if (element.row._record === record) {
1289 element.row.highlight(this._searchRegExp, this._highlightDomChanges);
1295 _clearHighlight: function()
1297 if (this._highlightDomChanges)
1298 WebInspector.revertDomChanges(this._highlightDomChanges);
1299 this._highlightDomChanges = [];
1303 * @param {boolean} revealRecord
1304 * @param {boolean} shouldJump
1306 _updateSearchHighlight: function(revealRecord, shouldJump)
1308 if (this._searchFilter || !this._searchRegExp) {
1309 this._clearHighlight();
1313 if (!this._searchResults)
1314 this._updateSearchResults(shouldJump);
1315 this._highlightSelectedSearchResult(revealRecord);
1318 _updateSearchResults: function(shouldJump)
1320 var searchRegExp = this._searchRegExp;
1325 var presentationModel = this._presentationModel;
1327 function processRecord(record)
1329 if (presentationModel.isVisible(record) && WebInspector.TimelineRecordListRow.testContentMatching(record, searchRegExp))
1330 matches.push(record);
1333 WebInspector.TimelinePresentationModel.forAllRecords(presentationModel.rootRecord().children, processRecord);
1335 var matchesCount = matches.length;
1337 this._searchResults = matches;
1338 this._searchableView.updateSearchMatchesCount(matchesCount);
1340 var selectedIndex = matches.indexOf(this._selectedSearchResult);
1341 if (shouldJump && selectedIndex === -1)
1343 this._selectSearchResult(selectedIndex);
1345 this._searchableView.updateSearchMatchesCount(0);
1346 delete this._selectedSearchResult;
1350 searchCanceled: function()
1352 this._clearHighlight();
1353 delete this._searchResults;
1354 delete this._selectedSearchResult;
1355 delete this._searchRegExp;
1359 * @param {string} query
1360 * @param {boolean} shouldJump
1362 performSearch: function(query, shouldJump)
1364 this._searchRegExp = createPlainTextSearchRegex(query, "i");
1365 delete this._searchResults;
1366 this._updateSearchHighlight(true, shouldJump);
1369 __proto__: WebInspector.View.prototype
1374 * @param {!WebInspector.TimelineModel} model
1375 * @implements {WebInspector.TimelineGrid.Calculator}
1377 WebInspector.TimelineCalculator = function(model)
1379 this._model = model;
1382 WebInspector.TimelineCalculator._minWidth = 5;
1384 WebInspector.TimelineCalculator.prototype = {
1386 * @param {number} time
1389 computePosition: function(time)
1391 return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this.paddingLeft;
1395 * @return {!{start: number, end: number, endWithChildren: number, cpuWidth: number}}
1397 computeBarGraphPercentages: function(record)
1399 var start = (record.startTime - this._minimumBoundary) / this.boundarySpan() * 100;
1400 var end = (record.startTime + record.selfTime - this._minimumBoundary) / this.boundarySpan() * 100;
1401 var endWithChildren = (record.lastChildEndTime - this._minimumBoundary) / this.boundarySpan() * 100;
1402 var cpuWidth = record.coalesced ? endWithChildren - start : record.cpuTime / this.boundarySpan() * 100;
1403 return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth};
1407 * @return {!{left: number, width: number, widthWithChildren: number, cpuWidth: number}}
1409 computeBarGraphWindowPosition: function(record)
1411 var percentages = this.computeBarGraphPercentages(record);
1412 var widthAdjustment = 0;
1414 var left = this.computePosition(record.startTime);
1415 var width = (percentages.end - percentages.start) / 100 * this._workingArea;
1416 if (width < WebInspector.TimelineCalculator._minWidth) {
1417 widthAdjustment = WebInspector.TimelineCalculator._minWidth - width;
1418 width = WebInspector.TimelineCalculator._minWidth;
1420 var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * this._workingArea + widthAdjustment;
1421 var cpuWidth = percentages.cpuWidth / 100 * this._workingArea + widthAdjustment;
1422 if (percentages.endWithChildren > percentages.end)
1423 widthWithChildren += widthAdjustment;
1424 return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth};
1427 setWindow: function(minimumBoundary, maximumBoundary)
1429 this._minimumBoundary = minimumBoundary;
1430 this._maximumBoundary = maximumBoundary;
1434 * @param {number} paddingLeft
1435 * @param {number} clientWidth
1437 setDisplayWindow: function(paddingLeft, clientWidth)
1439 this._workingArea = clientWidth - WebInspector.TimelineCalculator._minWidth - paddingLeft;
1440 this.paddingLeft = paddingLeft;
1444 * @param {number} value
1445 * @param {boolean=} hires
1448 formatTime: function(value, hires)
1450 return Number.secondsToString(value + this._minimumBoundary - this._model.minimumRecordTime(), hires);
1456 maximumBoundary: function()
1458 return this._maximumBoundary;
1464 minimumBoundary: function()
1466 return this._minimumBoundary;
1472 zeroTime: function()
1474 return this._model.minimumRecordTime();
1480 boundarySpan: function()
1482 return this._maximumBoundary - this._minimumBoundary;
1488 * @param {function(!WebInspector.TimelinePresentationModel.Record)} selectRecord
1489 * @param {function()} scheduleRefresh
1491 WebInspector.TimelineRecordListRow = function(selectRecord, scheduleRefresh)
1493 this.element = document.createElement("div");
1494 this.element.row = this;
1495 this.element.style.cursor = "pointer";
1496 this.element.addEventListener("click", this._onClick.bind(this), false);
1497 this.element.addEventListener("mouseover", this._onMouseOver.bind(this), false);
1498 this.element.addEventListener("mouseout", this._onMouseOut.bind(this), false);
1500 // Warning is float right block, it goes first.
1501 this._warningElement = this.element.createChild("div", "timeline-tree-item-warning hidden");
1503 this._expandArrowElement = this.element.createChild("div", "timeline-tree-item-expand-arrow");
1504 this._expandArrowElement.addEventListener("click", this._onExpandClick.bind(this), false);
1505 var iconElement = this.element.createChild("span", "timeline-tree-icon");
1506 this._typeElement = this.element.createChild("span", "type");
1508 this._dataElement = this.element.createChild("span", "data dimmed");
1509 this._scheduleRefresh = scheduleRefresh;
1510 this._selectRecord = selectRecord;
1513 WebInspector.TimelineRecordListRow.prototype = {
1514 update: function(record, offset)
1516 this._record = record;
1517 this._offset = offset;
1519 this.element.className = "timeline-tree-item timeline-category-" + record.category.name;
1520 var paddingLeft = 5;
1522 for (var currentRecord = record.parent ? record.parent.parent : null; currentRecord; currentRecord = currentRecord.parent)
1523 paddingLeft += 12 / (Math.max(1, step++));
1524 this.element.style.paddingLeft = paddingLeft + "px";
1525 if (record.isBackground)
1526 this.element.classList.add("background");
1528 this._typeElement.textContent = record.title;
1530 if (this._dataElement.firstChild)
1531 this._dataElement.removeChildren();
1533 this._warningElement.enableStyleClass("hidden", !record.hasWarnings() && !record.childHasWarnings());
1534 this._warningElement.enableStyleClass("timeline-tree-item-child-warning", record.childHasWarnings() && !record.hasWarnings());
1536 if (record.detailsNode())
1537 this._dataElement.appendChild(record.detailsNode());
1538 this._expandArrowElement.enableStyleClass("parent", record.children && record.children.length);
1539 this._expandArrowElement.enableStyleClass("expanded", record.visibleChildrenCount);
1540 this._record.setUserObject("WebInspector.TimelineRecordListRow", this);
1543 highlight: function(regExp, domChanges)
1545 var matchInfo = this.element.textContent.match(regExp);
1547 WebInspector.highlightSearchResult(this.element, matchInfo.index, matchInfo[0].length, domChanges);
1552 this.element.remove();
1556 * @param {!Event} event
1558 _onExpandClick: function(event)
1560 this._record.collapsed = !this._record.collapsed;
1561 this._record.clicked = true;
1562 this._scheduleRefresh();
1563 event.consume(true);
1567 * @param {?Event} event
1569 _onClick: function(event)
1571 this._selectRecord(this._record);
1575 * @param {boolean} selected
1577 renderAsSelected: function(selected)
1579 this.element.enableStyleClass("selected", selected);
1583 * @param {?Event} event
1585 _onMouseOver: function(event)
1587 this.element.classList.add("hovered");
1588 var graphRow = /** @type {!WebInspector.TimelineRecordGraphRow} */ (this._record.getUserObject("WebInspector.TimelineRecordGraphRow"));
1589 graphRow.element.classList.add("hovered");
1593 * @param {?Event} event
1595 _onMouseOut: function(event)
1597 this.element.classList.remove("hovered");
1598 var graphRow = /** @type {!WebInspector.TimelineRecordGraphRow} */ (this._record.getUserObject("WebInspector.TimelineRecordGraphRow"));
1599 graphRow.element.classList.remove("hovered");
1604 * @param {!WebInspector.TimelinePresentationModel.Record} record
1605 * @param {!RegExp} regExp
1607 WebInspector.TimelineRecordListRow.testContentMatching = function(record, regExp)
1609 var toSearchText = record.title;
1610 if (record.detailsNode())
1611 toSearchText += " " + record.detailsNode().textContent;
1612 return regExp.test(toSearchText);
1617 * @param {function(!WebInspector.TimelinePresentationModel.Record)} selectRecord
1618 * @param {function()} scheduleRefresh
1620 WebInspector.TimelineRecordGraphRow = function(graphContainer, selectRecord, scheduleRefresh)
1622 this.element = document.createElement("div");
1623 this.element.row = this;
1624 this.element.addEventListener("mouseover", this._onMouseOver.bind(this), false);
1625 this.element.addEventListener("mouseout", this._onMouseOut.bind(this), false);
1626 this.element.addEventListener("click", this._onClick.bind(this), false);
1628 this._barAreaElement = document.createElement("div");
1629 this._barAreaElement.className = "timeline-graph-bar-area";
1630 this.element.appendChild(this._barAreaElement);
1632 this._barWithChildrenElement = document.createElement("div");
1633 this._barWithChildrenElement.className = "timeline-graph-bar with-children";
1634 this._barWithChildrenElement.row = this;
1635 this._barAreaElement.appendChild(this._barWithChildrenElement);
1637 this._barCpuElement = document.createElement("div");
1638 this._barCpuElement.className = "timeline-graph-bar cpu"
1639 this._barCpuElement.row = this;
1640 this._barAreaElement.appendChild(this._barCpuElement);
1642 this._barElement = document.createElement("div");
1643 this._barElement.className = "timeline-graph-bar";
1644 this._barElement.row = this;
1645 this._barAreaElement.appendChild(this._barElement);
1647 this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer);
1649 this._selectRecord = selectRecord;
1650 this._scheduleRefresh = scheduleRefresh;
1653 WebInspector.TimelineRecordGraphRow.prototype = {
1654 update: function(record, calculator, expandOffset, index)
1656 this._record = record;
1657 this.element.className = "timeline-graph-side timeline-category-" + record.category.name;
1658 if (record.isBackground)
1659 this.element.classList.add("background");
1661 var barPosition = calculator.computeBarGraphWindowPosition(record);
1662 this._barWithChildrenElement.style.left = barPosition.left + "px";
1663 this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px";
1664 this._barElement.style.left = barPosition.left + "px";
1665 this._barElement.style.width = barPosition.width + "px";
1666 this._barCpuElement.style.left = barPosition.left + "px";
1667 this._barCpuElement.style.width = barPosition.cpuWidth + "px";
1668 this._expandElement._update(record, index, barPosition.left - expandOffset, barPosition.width);
1670 this._record.setUserObject("WebInspector.TimelineRecordGraphRow", this);
1674 * @param {?Event} event
1676 _onClick: function(event)
1678 // check if we click arrow and expand if yes.
1679 if (this._expandElement._arrow.containsEventPoint(event))
1681 this._selectRecord(this._record);
1685 * @param {boolean} selected
1687 renderAsSelected: function(selected)
1689 this.element.enableStyleClass("selected", selected);
1694 this._record.collapsed = !this._record.collapsed;
1695 this._record.clicked = true;
1696 this._scheduleRefresh();
1700 * @param {?Event} event
1702 _onMouseOver: function(event)
1704 this.element.classList.add("hovered");
1705 var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (this._record.getUserObject("WebInspector.TimelineRecordListRow"));
1706 listRow.element.classList.add("hovered");
1710 * @param {?Event} event
1712 _onMouseOut: function(event)
1714 this.element.classList.remove("hovered");
1715 var listRow = /** @type {!WebInspector.TimelineRecordListRow} */ (this._record.getUserObject("WebInspector.TimelineRecordListRow"));
1716 listRow.element.classList.remove("hovered");
1721 this.element.remove();
1722 this._expandElement._dispose();
1729 WebInspector.TimelineExpandableElement = function(container)
1731 this._element = container.createChild("div", "timeline-expandable");
1732 this._element.createChild("div", "timeline-expandable-left");
1733 this._arrow = this._element.createChild("div", "timeline-expandable-arrow");
1736 WebInspector.TimelineExpandableElement.prototype = {
1737 _update: function(record, index, left, width)
1739 const rowHeight = WebInspector.TimelinePanel.rowHeight;
1740 if (record.visibleChildrenCount || record.expandable) {
1741 this._element.style.top = index * rowHeight + "px";
1742 this._element.style.left = left + "px";
1743 this._element.style.width = Math.max(12, width + 25) + "px";
1744 if (!record.collapsed) {
1745 this._element.style.height = (record.visibleChildrenCount + 1) * rowHeight + "px";
1746 this._element.classList.add("timeline-expandable-expanded");
1747 this._element.classList.remove("timeline-expandable-collapsed");
1749 this._element.style.height = rowHeight + "px";
1750 this._element.classList.add("timeline-expandable-collapsed");
1751 this._element.classList.remove("timeline-expandable-expanded");
1753 this._element.classList.remove("hidden");
1755 this._element.classList.add("hidden");
1758 _dispose: function()
1760 this._element.remove();
1766 * @implements {WebInspector.TimelinePresentationModel.Filter}
1768 WebInspector.TimelineCategoryFilter = function()
1772 WebInspector.TimelineCategoryFilter.prototype = {
1774 * @param {!WebInspector.TimelinePresentationModel.Record} record
1777 accept: function(record)
1779 return !record.category.hidden && record.type !== WebInspector.TimelineModel.RecordType.BeginFrame;
1785 * @implements {WebInspector.TimelinePresentationModel.Filter}
1787 WebInspector.TimelineIsLongFilter = function()
1789 this._minimumRecordDuration = 0;
1792 WebInspector.TimelineIsLongFilter.prototype = {
1794 * @param {number} value
1796 setMinimumRecordDuration: function(value)
1798 this._minimumRecordDuration = value;
1802 * @param {!WebInspector.TimelinePresentationModel.Record} record
1805 accept: function(record)
1807 return this._minimumRecordDuration ? ((record.lastChildEndTime - record.startTime) >= this._minimumRecordDuration) : true;
1812 * @param {!RegExp} regExp
1814 * @implements {WebInspector.TimelinePresentationModel.Filter}
1816 WebInspector.TimelineSearchFilter = function(regExp)
1818 this._regExp = regExp;
1821 WebInspector.TimelineSearchFilter.prototype = {
1823 * @param {!WebInspector.TimelinePresentationModel.Record} record
1826 accept: function(record)
1828 return WebInspector.TimelineRecordListRow.testContentMatching(record, this._regExp);
1834 * @implements {WebInspector.TimelinePresentationModel.Filter}
1836 WebInspector.TimelineWindowFilter = function()
1841 WebInspector.TimelineWindowFilter.prototype = {
1844 this._windowStartTime = 0;
1845 this._windowEndTime = Infinity;
1848 setWindowTimes: function(windowStartTime, windowEndTime)
1850 this._windowStartTime = windowStartTime;
1851 this._windowEndTime = windowEndTime;
1855 * @param {!WebInspector.TimelinePresentationModel.Record} record
1858 accept: function(record)
1860 return record.lastChildEndTime >= this._windowStartTime && record.startTime <= this._windowEndTime;
1866 * @extends {WebInspector.View}
1868 WebInspector.TimelineDetailsView = function()
1870 WebInspector.View.call(this);
1871 this.element.classList.add("timeline-details-view");
1872 this._titleElement = this.element.createChild("div", "timeline-details-view-title");
1873 this._titleElement.textContent = WebInspector.UIString("DETAILS");
1874 this._contentElement = this.element.createChild("div", "timeline-details-view-body");
1877 WebInspector.TimelineDetailsView.prototype = {
1879 * @return {!Element}
1881 titleElement: function()
1883 return this._titleElement;
1887 * @param {string} title
1888 * @param {!Node} node
1890 setContent: function(title, node)
1892 this._titleElement.textContent = WebInspector.UIString("DETAILS: %s", title);
1893 this._contentElement.removeChildren();
1894 this._contentElement.appendChild(node);
1898 * @param {boolean} vertical
1900 setVertical: function(vertical)
1902 this._contentElement.enableStyleClass("hbox", !vertical);
1903 this._contentElement.enableStyleClass("vbox", vertical);
1906 __proto__: WebInspector.View.prototype