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 * @extends {WebInspector.HBox}
35 * @implements {WebInspector.TimelineModeView}
36 * @param {!WebInspector.TimelineModeViewDelegate} delegate
37 * @param {!WebInspector.TimelineModel} model
39 WebInspector.TimelineView = function(delegate, model)
41 WebInspector.HBox.call(this);
42 this.element.classList.add("timeline-view");
44 this._delegate = delegate;
46 this._presentationModel = new WebInspector.TimelinePresentationModel(model);
47 this._calculator = new WebInspector.TimelineCalculator(model);
48 this._linkifier = new WebInspector.Linkifier();
50 this._boundariesAreValid = true;
53 this._recordsView = this._createRecordsView();
54 this._recordsView.addEventListener(WebInspector.SplitView.Events.SidebarSizeChanged, this._sidebarResized, this);
55 this._recordsView.show(this.element);
56 this._headerElement = this.element.createChild("div", "fill");
57 this._headerElement.id = "timeline-graph-records-header";
59 // Create gpu tasks containers.
60 this._cpuBarsElement = this._headerElement.createChild("div", "timeline-utilization-strip");
61 if (WebInspector.experimentsSettings.gpuTimeline.isEnabled())
62 this._gpuBarsElement = this._headerElement.createChild("div", "timeline-utilization-strip gpu");
64 this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
66 this.element.addEventListener("mousemove", this._mouseMove.bind(this), false);
67 this.element.addEventListener("mouseout", this._mouseOut.bind(this), false);
68 this.element.addEventListener("keydown", this._keyDown.bind(this), false);
70 this._expandOffset = 15;
73 WebInspector.TimelineView.prototype = {
75 * @param {?WebInspector.TimelineFrameModel} frameModel
77 setFrameModel: function(frameModel)
79 this._frameModel = frameModel;
83 * @return {!WebInspector.SplitView}
85 _createRecordsView: function()
87 var recordsView = new WebInspector.SplitView(true, false, "timelinePanelRecorsSplitViewState");
88 this._containerElement = recordsView.element;
89 this._containerElement.tabIndex = 0;
90 this._containerElement.id = "timeline-container";
91 this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false);
93 // Create records list in the records sidebar.
94 recordsView.sidebarElement().createChild("div", "timeline-records-title").textContent = WebInspector.UIString("RECORDS");
95 this._sidebarListElement = recordsView.sidebarElement().createChild("div", "timeline-records-list");
97 // Create grid in the records main area.
98 this._gridContainer = new WebInspector.VBoxWithResizeCallback(this._onViewportResize.bind(this));
99 this._gridContainer.element.id = "resources-container-content";
100 this._gridContainer.show(recordsView.mainElement());
101 this._timelineGrid = new WebInspector.TimelineGrid();
102 this._gridContainer.element.appendChild(this._timelineGrid.element);
104 this._itemsGraphsElement = this._gridContainer.element.createChild("div");
105 this._itemsGraphsElement.id = "timeline-graphs";
107 // Create gap elements
108 this._topGapElement = this._itemsGraphsElement.createChild("div", "timeline-gap");
109 this._graphRowsElement = this._itemsGraphsElement.createChild("div");
110 this._bottomGapElement = this._itemsGraphsElement.createChild("div", "timeline-gap");
111 this._expandElements = this._itemsGraphsElement.createChild("div");
112 this._expandElements.id = "orphan-expand-elements";
117 _rootRecord: function()
119 return this._presentationModel.rootRecord();
122 _updateEventDividers: function()
124 this._timelineGrid.removeEventDividers();
125 var clientWidth = this._graphRowsElementWidth;
127 var eventDividerRecords = this._model.eventDividerRecords();
129 for (var i = 0; i < eventDividerRecords.length; ++i) {
130 var record = eventDividerRecords[i];
131 var positions = this._calculator.computeBarGraphWindowPosition(record);
132 var dividerPosition = Math.round(positions.left);
133 if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition])
135 var divider = WebInspector.TimelineUIUtils.createEventDivider(record.type, WebInspector.TimelineUIUtils.recordTitle(record));
136 divider.style.left = dividerPosition + "px";
137 dividers[dividerPosition] = divider;
139 this._timelineGrid.addEventDividers(dividers);
142 _updateFrameBars: function(frames)
144 var clientWidth = this._graphRowsElementWidth;
145 if (this._frameContainer)
146 this._frameContainer.removeChildren();
148 const frameContainerBorderWidth = 1;
149 this._frameContainer = document.createElement("div");
150 this._frameContainer.classList.add("fill");
151 this._frameContainer.classList.add("timeline-frame-container");
152 this._frameContainer.style.height = WebInspector.TimelinePanel.rowHeight + frameContainerBorderWidth + "px";
153 this._frameContainer.addEventListener("dblclick", this._onFrameDoubleClicked.bind(this), false);
158 for (var i = 0; i < frames.length; ++i) {
159 var frame = frames[i];
160 var frameStart = this._calculator.computePosition(frame.startTime);
161 var frameEnd = this._calculator.computePosition(frame.endTime);
163 var frameStrip = document.createElement("div");
164 frameStrip.className = "timeline-frame-strip";
165 var actualStart = Math.max(frameStart, 0);
166 var width = frameEnd - actualStart;
167 frameStrip.style.left = actualStart + "px";
168 frameStrip.style.width = width + "px";
169 frameStrip._frame = frame;
171 const minWidthForFrameInfo = 60;
172 if (width > minWidthForFrameInfo)
173 frameStrip.textContent = Number.millisToString(frame.endTime - frame.startTime, true);
175 this._frameContainer.appendChild(frameStrip);
177 if (actualStart > 0) {
178 var frameMarker = WebInspector.TimelineUIUtils.createEventDivider(WebInspector.TimelineModel.RecordType.BeginFrame);
179 frameMarker.style.left = frameStart + "px";
180 dividers.push(frameMarker);
183 this._timelineGrid.addEventDividers(dividers);
184 this._headerElement.appendChild(this._frameContainer);
187 _onFrameDoubleClicked: function(event)
189 var frameBar = event.target.enclosingNodeOrSelfWithClass("timeline-frame-strip");
192 this._delegate.requestWindowTimes(frameBar._frame.startTime, frameBar._frame.endTime);
196 * @param {!WebInspector.TimelineModel.Record} record
198 addRecord: function(record)
200 this._presentationModel.addRecord(record);
201 this._invalidateAndScheduleRefresh(false, false);
205 * @param {number} width
207 setSidebarSize: function(width)
209 this._recordsView.setSidebarSize(width);
213 * @param {!WebInspector.Event} event
215 _sidebarResized: function(event)
217 this.dispatchEventToListeners(WebInspector.SplitView.Events.SidebarSizeChanged, event.data);
220 _onViewportResize: function()
222 this._resize(this._recordsView.sidebarSize());
226 * @param {number} sidebarWidth
228 _resize: function(sidebarWidth)
230 this._closeRecordDetails();
231 this._graphRowsElementWidth = this._graphRowsElement.offsetWidth;
232 this._containerElementHeight = this._containerElement.clientHeight;
233 this._headerElement.style.left = sidebarWidth + "px";
234 this._headerElement.style.width = this._itemsGraphsElement.offsetWidth + "px";
235 this._scheduleRefresh(false, true);
238 _resetView: function()
240 this._windowStartTime = -1;
241 this._windowEndTime = -1;
242 this._boundariesAreValid = false;
243 this._adjustScrollPosition(0);
244 this._linkifier.reset();
245 this._closeRecordDetails();
246 this._automaticallySizeWindow = true;
247 this._presentationModel.reset();
253 this._invalidateAndScheduleRefresh(true, true);
257 * @return {!Array.<!Element>}
259 elementsToRestoreScrollPositionsFor: function()
261 return [this._containerElement];
265 * @param {?RegExp} textFilter
267 refreshRecords: function(textFilter)
269 this._presentationModel.reset();
270 var records = this._model.records();
271 for (var i = 0; i < records.length; ++i)
272 this.addRecord(records[i]);
273 this._automaticallySizeWindow = false;
274 this._presentationModel.setTextFilter(textFilter);
275 this._invalidateAndScheduleRefresh(false, true);
280 WebInspector.View.prototype.wasShown.call(this);
281 this._onViewportResize();
287 this._closeRecordDetails();
288 WebInspector.View.prototype.willHide.call(this);
291 _onScroll: function(event)
293 this._closeRecordDetails();
294 this._scrollTop = this._containerElement.scrollTop;
295 var dividersTop = Math.max(0, this._scrollTop);
296 this._timelineGrid.setScrollAndDividerTop(this._scrollTop, dividersTop);
297 this._scheduleRefresh(true, true);
301 * @param {boolean} preserveBoundaries
302 * @param {boolean} userGesture
304 _invalidateAndScheduleRefresh: function(preserveBoundaries, userGesture)
306 this._presentationModel.invalidateFilteredRecords();
307 this._scheduleRefresh(preserveBoundaries, userGesture);
311 * @param {?WebInspector.TimelinePresentationModel.Record} presentationRecord
313 _selectRecord: function(presentationRecord)
315 if (presentationRecord && presentationRecord.coalesced()) {
316 // Presentation record does not have model record to highlight.
317 this._innerSetSelectedRecord(presentationRecord);
318 var aggregatedStats = {};
319 var presentationChildren = presentationRecord.presentationChildren();
320 for (var i = 0; i < presentationChildren.length; ++i)
321 WebInspector.TimelineUIUtils.aggregateTimeByCategory(aggregatedStats, presentationChildren[i].record().aggregatedStats);
322 var idle = presentationRecord.record().endTime - presentationRecord.record().startTime;
323 for (var category in aggregatedStats)
324 idle -= aggregatedStats[category];
325 aggregatedStats["idle"] = idle;
326 this._delegate.showAggregatedStatsInDetails(WebInspector.TimelineUIUtils.recordStyle(presentationRecord.record()).title, aggregatedStats);
329 this._delegate.selectRecord(presentationRecord ? presentationRecord.record() : null);
333 * @param {?WebInspector.TimelineModel.Record} record
335 setSelectedRecord: function(record)
337 this._innerSetSelectedRecord(this._presentationModel.toPresentationRecord(record));
341 * @param {?WebInspector.TimelinePresentationModel.Record} presentationRecord
343 _innerSetSelectedRecord: function(presentationRecord)
345 if (presentationRecord === this._lastSelectedRecord)
348 // Remove selection rendering.p
349 if (this._lastSelectedRecord) {
350 if (this._lastSelectedRecord.listRow())
351 this._lastSelectedRecord.listRow().renderAsSelected(false);
352 if (this._lastSelectedRecord.graphRow())
353 this._lastSelectedRecord.graphRow().renderAsSelected(false);
356 this._lastSelectedRecord = presentationRecord;
357 if (!presentationRecord)
360 this._innerRevealRecord(presentationRecord);
361 if (presentationRecord.listRow())
362 presentationRecord.listRow().renderAsSelected(true);
363 if (presentationRecord.graphRow())
364 presentationRecord.graphRow().renderAsSelected(true);
368 * @param {number} startTime
369 * @param {number} endTime
371 setWindowTimes: function(startTime, endTime)
373 this._windowStartTime = startTime;
374 this._windowEndTime = endTime;
375 this._presentationModel.setWindowTimes(startTime, endTime);
376 this._automaticallySizeWindow = false;
377 this._invalidateAndScheduleRefresh(false, true);
378 this._selectRecord(null);
382 * @param {boolean} preserveBoundaries
383 * @param {boolean} userGesture
385 _scheduleRefresh: function(preserveBoundaries, userGesture)
387 this._closeRecordDetails();
388 this._boundariesAreValid &= preserveBoundaries;
390 if (!this.isShowing())
393 if (preserveBoundaries || userGesture)
396 if (!this._refreshTimeout)
397 this._refreshTimeout = setTimeout(this._refresh.bind(this), 300);
403 if (this._refreshTimeout) {
404 clearTimeout(this._refreshTimeout);
405 delete this._refreshTimeout;
407 var windowStartTime = this._windowStartTime;
408 var windowEndTime = this._windowEndTime;
409 this._timelinePaddingLeft = this._expandOffset;
410 if (windowStartTime === -1)
411 windowStartTime = this._model.minimumRecordTime();
412 if (windowEndTime === -1)
413 windowEndTime = this._model.maximumRecordTime();
414 this._calculator.setWindow(windowStartTime, windowEndTime);
415 this._calculator.setDisplayWindow(this._timelinePaddingLeft, this._graphRowsElementWidth);
417 this._refreshRecords();
418 if (!this._boundariesAreValid) {
419 this._updateEventDividers();
420 if (this._frameContainer)
421 this._frameContainer.remove();
422 if (this._frameModel) {
423 var frames = this._frameModel.filteredFrames(windowStartTime, windowEndTime);
424 const maxFramesForFrameBars = 30;
425 if (frames.length && frames.length < maxFramesForFrameBars) {
426 this._timelineGrid.removeDividers();
427 this._updateFrameBars(frames);
429 this._timelineGrid.updateDividers(this._calculator);
432 this._timelineGrid.updateDividers(this._calculator);
433 this._refreshAllUtilizationBars();
435 this._boundariesAreValid = true;
439 * @param {!WebInspector.TimelinePresentationModel.Record} recordToReveal
441 _innerRevealRecord: function(recordToReveal)
443 var needRefresh = false;
444 // Expand all ancestors.
445 for (var parent = recordToReveal.presentationParent(); parent !== this._rootRecord(); parent = parent.presentationParent()) {
446 if (!parent.collapsed())
448 this._presentationModel.invalidateFilteredRecords();
449 parent.setCollapsed(false);
452 var recordsInWindow = this._presentationModel.filteredRecords();
453 var index = recordsInWindow.indexOf(recordToReveal);
455 var itemOffset = index * WebInspector.TimelinePanel.rowHeight;
456 var visibleTop = this._scrollTop - WebInspector.TimelinePanel.headerHeight;
457 var visibleBottom = visibleTop + this._containerElementHeight - WebInspector.TimelinePanel.rowHeight;
458 if (itemOffset < visibleTop)
459 this._containerElement.scrollTop = itemOffset;
460 else if (itemOffset > visibleBottom)
461 this._containerElement.scrollTop = itemOffset - this._containerElementHeight + WebInspector.TimelinePanel.headerHeight + WebInspector.TimelinePanel.rowHeight;
462 else if (needRefresh)
463 this._refreshRecords();
466 _refreshRecords: function()
468 var recordsInWindow = this._presentationModel.filteredRecords();
470 // Calculate the visible area.
471 var visibleTop = this._scrollTop;
472 var visibleBottom = visibleTop + this._containerElementHeight;
474 var rowHeight = WebInspector.TimelinePanel.rowHeight;
475 var headerHeight = WebInspector.TimelinePanel.headerHeight;
477 // Convert visible area to visible indexes. Always include top-level record for a visible nested record.
478 var startIndex = Math.max(0, Math.min(Math.floor((visibleTop - headerHeight) / rowHeight), recordsInWindow.length - 1));
479 var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));
480 var lastVisibleLine = Math.max(0, Math.floor((visibleBottom - headerHeight) / rowHeight));
481 if (this._automaticallySizeWindow && recordsInWindow.length > lastVisibleLine) {
482 this._automaticallySizeWindow = false;
483 this._selectRecord(null);
484 // If we're at the top, always use real timeline start as a left window bound so that expansion arrow padding logic works.
485 var windowStartTime = startIndex ? recordsInWindow[startIndex].record().startTime : this._model.minimumRecordTime();
486 var windowEndTime = recordsInWindow[Math.max(0, lastVisibleLine - 1)].record().endTime;
487 this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
488 recordsInWindow = this._presentationModel.filteredRecords();
489 endIndex = Math.min(recordsInWindow.length, lastVisibleLine);
492 // Resize gaps first.
493 this._topGapElement.style.height = (startIndex * rowHeight) + "px";
494 this._recordsView.sidebarElement().firstElementChild.style.flexBasis = (startIndex * rowHeight + headerHeight) + "px";
495 this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px";
496 var rowsHeight = headerHeight + recordsInWindow.length * rowHeight;
497 var totalHeight = Math.max(this._containerElementHeight, rowsHeight);
499 this._recordsView.mainElement().style.height = totalHeight + "px";
500 this._recordsView.sidebarElement().style.height = totalHeight + "px";
501 this._recordsView.resizerElement().style.height = totalHeight + "px";
503 // Update visible rows.
504 var listRowElement = this._sidebarListElement.firstChild;
505 var width = this._graphRowsElementWidth;
506 this._itemsGraphsElement.removeChild(this._graphRowsElement);
507 var graphRowElement = this._graphRowsElement.firstChild;
508 var scheduleRefreshCallback = this._invalidateAndScheduleRefresh.bind(this, true, true);
509 var selectRecordCallback = this._selectRecord.bind(this);
510 this._itemsGraphsElement.removeChild(this._expandElements);
511 this._expandElements.removeChildren();
513 for (var i = 0; i < endIndex; ++i) {
514 var record = recordsInWindow[i];
516 if (i < startIndex) {
517 var lastChildIndex = i + record.visibleChildrenCount();
518 if (lastChildIndex >= startIndex && lastChildIndex < endIndex) {
519 var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements);
520 var positions = this._calculator.computeBarGraphWindowPosition(record);
521 expandElement._update(record, i, positions.left - this._expandOffset, positions.width);
524 if (!listRowElement) {
525 listRowElement = new WebInspector.TimelineRecordListRow(this._linkifier, selectRecordCallback, scheduleRefreshCallback).element;
526 this._sidebarListElement.appendChild(listRowElement);
528 if (!graphRowElement) {
529 graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, selectRecordCallback, scheduleRefreshCallback).element;
530 this._graphRowsElement.appendChild(graphRowElement);
533 listRowElement.row.update(record, visibleTop);
534 graphRowElement.row.update(record, this._calculator, this._expandOffset, i);
535 if (this._lastSelectedRecord === record) {
536 listRowElement.row.renderAsSelected(true);
537 graphRowElement.row.renderAsSelected(true);
540 listRowElement = listRowElement.nextSibling;
541 graphRowElement = graphRowElement.nextSibling;
545 // Remove extra rows.
546 while (listRowElement) {
547 var nextElement = listRowElement.nextSibling;
548 listRowElement.row.dispose();
549 listRowElement = nextElement;
551 while (graphRowElement) {
552 var nextElement = graphRowElement.nextSibling;
553 graphRowElement.row.dispose();
554 graphRowElement = nextElement;
557 this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement);
558 this._itemsGraphsElement.appendChild(this._expandElements);
559 this._adjustScrollPosition(recordsInWindow.length * rowHeight + headerHeight);
561 return recordsInWindow.length;
564 _refreshAllUtilizationBars: function()
566 this._refreshUtilizationBars(WebInspector.UIString("CPU"), this._model.mainThreadTasks(), this._cpuBarsElement);
567 if (WebInspector.experimentsSettings.gpuTimeline.isEnabled())
568 this._refreshUtilizationBars(WebInspector.UIString("GPU"), this._model.gpuThreadTasks(), this._gpuBarsElement);
572 * @param {string} name
573 * @param {!Array.<!WebInspector.TimelineModel.Record>} tasks
574 * @param {?Element} container
576 _refreshUtilizationBars: function(name, tasks, container)
584 var minWidth = WebInspector.TimelineCalculator._minWidth;
585 var widthAdjustment = minWidth / 2;
587 var width = this._graphRowsElementWidth;
588 var boundarySpan = this._windowEndTime - this._windowStartTime;
589 var scale = boundarySpan / (width - minWidth - this._timelinePaddingLeft);
590 var startTime = (this._windowStartTime - this._timelinePaddingLeft * scale);
591 var endTime = startTime + width * scale;
594 * @param {number} value
595 * @param {!WebInspector.TimelineModel.Record} task
598 function compareEndTime(value, task)
600 return value < task.endTime ? -1 : 1;
603 var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, tasks, compareEndTime);
605 var foreignStyle = "gpu-task-foreign";
606 var element = container.firstChild;
611 for (; taskIndex < tasks.length; ++taskIndex) {
612 var task = tasks[taskIndex];
613 if (task.startTime > endTime)
616 var left = Math.max(0, this._calculator.computePosition(task.startTime) + barOffset - widthAdjustment);
617 var right = Math.min(width, this._calculator.computePosition(task.endTime || 0) + barOffset + widthAdjustment);
620 var gap = Math.floor(left) - Math.ceil(lastRight);
622 if (!task.data["foreign"])
623 lastElement.classList.remove(foreignStyle);
625 lastElement._tasksInfo.lastTaskIndex = taskIndex;
628 lastElement.style.width = (lastRight - lastLeft) + "px";
632 element = container.createChild("div", "timeline-graph-bar");
633 element.style.left = left + "px";
634 element._tasksInfo = {name: name, tasks: tasks, firstTaskIndex: taskIndex, lastTaskIndex: taskIndex};
635 if (task.data["foreign"])
636 element.classList.add(foreignStyle);
639 lastElement = element;
640 element = element.nextSibling;
644 lastElement.style.width = (lastRight - lastLeft) + "px";
647 var nextElement = element.nextSibling;
648 element._tasksInfo = null;
649 container.removeChild(element);
650 element = nextElement;
654 _adjustScrollPosition: function(totalHeight)
656 // Prevent the container from being scrolled off the end.
657 if ((this._scrollTop + this._containerElementHeight) > totalHeight + 1)
658 this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight);
661 _getPopoverAnchor: function(element)
663 var anchor = element.enclosingNodeOrSelfWithClass("timeline-graph-bar");
664 if (anchor && anchor._tasksInfo)
666 return element.enclosingNodeOrSelfWithClass("timeline-frame-strip");
669 _mouseOut: function()
671 this._hideQuadHighlight();
677 _mouseMove: function(e)
679 var rowElement = e.target.enclosingNodeOrSelfWithClass("timeline-tree-item");
680 if (rowElement && rowElement.row && rowElement.row._record.record().highlightQuad)
681 this._highlightQuad(rowElement.row._record.record().highlightQuad);
683 this._hideQuadHighlight();
685 var taskBarElement = e.target.enclosingNodeOrSelfWithClass("timeline-graph-bar");
686 if (taskBarElement && taskBarElement._tasksInfo) {
687 var offset = taskBarElement.offsetLeft;
688 this._timelineGrid.showCurtains(offset >= 0 ? offset : 0, taskBarElement.offsetWidth);
690 this._timelineGrid.hideCurtains();
694 * @param {?Event} event
696 _keyDown: function(event)
698 if (!this._lastSelectedRecord || event.shiftKey || event.metaKey || event.ctrlKey)
701 var record = this._lastSelectedRecord;
702 var recordsInWindow = this._presentationModel.filteredRecords();
703 var index = recordsInWindow.indexOf(record);
704 var recordsInPage = Math.floor(this._containerElementHeight / WebInspector.TimelinePanel.rowHeight);
705 var rowHeight = WebInspector.TimelinePanel.rowHeight;
710 switch (event.keyIdentifier) {
712 if (record.presentationParent()) {
713 if ((!record.expandable() || record.collapsed()) && record.presentationParent() !== this._presentationModel.rootRecord()) {
714 this._selectRecord(record.presentationParent());
716 record.setCollapsed(true);
717 this._invalidateAndScheduleRefresh(true, true);
725 this._selectRecord(recordsInWindow[index]);
729 if (record.expandable() && record.collapsed()) {
730 record.setCollapsed(false);
731 this._invalidateAndScheduleRefresh(true, true);
733 if (++index >= recordsInWindow.length)
735 this._selectRecord(recordsInWindow[index]);
740 if (++index >= recordsInWindow.length)
742 this._selectRecord(recordsInWindow[index]);
746 index = Math.max(0, index - recordsInPage);
747 this._scrollTop = Math.max(0, this._scrollTop - recordsInPage * rowHeight);
748 this._containerElement.scrollTop = this._scrollTop;
749 this._selectRecord(recordsInWindow[index]);
753 index = Math.min(recordsInWindow.length - 1, index + recordsInPage);
754 this._scrollTop = Math.min(this._containerElement.scrollHeight - this._containerElementHeight, this._scrollTop + recordsInPage * rowHeight);
755 this._containerElement.scrollTop = this._scrollTop;
756 this._selectRecord(recordsInWindow[index]);
761 this._selectRecord(recordsInWindow[index]);
765 index = recordsInWindow.length - 1;
766 this._selectRecord(recordsInWindow[index]);
773 * @param {!Array.<number>} quad
775 _highlightQuad: function(quad)
777 if (this._highlightedQuad === quad)
779 this._highlightedQuad = quad;
780 DOMAgent.highlightQuad(quad, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA());
783 _hideQuadHighlight: function()
785 if (this._highlightedQuad) {
786 delete this._highlightedQuad;
787 DOMAgent.hideHighlight();
792 * @param {!Element} anchor
793 * @param {!WebInspector.Popover} popover
795 _showPopover: function(anchor, popover)
797 if (anchor.classList.contains("timeline-frame-strip")) {
798 var frame = anchor._frame;
799 popover.show(WebInspector.TimelineUIUtils.generatePopupContentForFrame(frame), anchor);
800 } else if (anchor._tasksInfo) {
801 popover.show(WebInspector.TimelineUIUtils.generateMainThreadBarPopupContent(this._model, anchor._tasksInfo), anchor, null, null, WebInspector.Popover.Orientation.Bottom);
804 function showCallback(popupContent)
806 popover.show(popupContent, anchor);
810 _closeRecordDetails: function()
812 this._popoverHelper.hidePopover();
816 * @param {?WebInspector.TimelineModel.Record} record
817 * @param {string=} regex
818 * @param {boolean=} selectRecord
820 highlightSearchResult: function(record, regex, selectRecord)
822 if (this._highlightDomChanges)
823 WebInspector.revertDomChanges(this._highlightDomChanges);
824 this._highlightDomChanges = [];
826 var presentationRecord = this._presentationModel.toPresentationRecord(record);
827 if (!presentationRecord)
831 this._selectRecord(presentationRecord);
833 for (var element = this._sidebarListElement.firstChild; element; element = element.nextSibling) {
834 if (element.row._record === presentationRecord) {
835 element.row.highlight(regex, this._highlightDomChanges);
841 __proto__: WebInspector.HBox.prototype
846 * @param {!WebInspector.TimelineModel} model
847 * @implements {WebInspector.TimelineGrid.Calculator}
849 WebInspector.TimelineCalculator = function(model)
854 WebInspector.TimelineCalculator._minWidth = 5;
856 WebInspector.TimelineCalculator.prototype = {
860 paddingLeft: function()
862 return this._paddingLeft;
866 * @param {number} time
869 computePosition: function(time)
871 return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this._paddingLeft;
875 * @return {!{start: number, end: number, endWithChildren: number, cpuWidth: number}}
877 computeBarGraphPercentages: function(record)
879 var start = (record.startTime - this._minimumBoundary) / this.boundarySpan() * 100;
880 var end = (record.startTime + record.selfTime - this._minimumBoundary) / this.boundarySpan() * 100;
881 var endWithChildren = (record.lastChildEndTime - this._minimumBoundary) / this.boundarySpan() * 100;
882 var cpuWidth = record.cpuTime / this.boundarySpan() * 100;
883 return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth};
887 * @return {!{left: number, width: number, widthWithChildren: number, cpuWidth: number}}
889 computeBarGraphWindowPosition: function(record)
891 var percentages = this.computeBarGraphPercentages(record);
892 var widthAdjustment = 0;
894 var left = this.computePosition(record.startTime);
895 var width = (percentages.end - percentages.start) / 100 * this._workingArea;
896 if (width < WebInspector.TimelineCalculator._minWidth) {
897 widthAdjustment = WebInspector.TimelineCalculator._minWidth - width;
898 width = WebInspector.TimelineCalculator._minWidth;
900 var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * this._workingArea + widthAdjustment;
901 var cpuWidth = percentages.cpuWidth / 100 * this._workingArea + widthAdjustment;
902 if (percentages.endWithChildren > percentages.end)
903 widthWithChildren += widthAdjustment;
904 return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth};
907 setWindow: function(minimumBoundary, maximumBoundary)
909 this._minimumBoundary = minimumBoundary;
910 this._maximumBoundary = maximumBoundary;
914 * @param {number} paddingLeft
915 * @param {number} clientWidth
917 setDisplayWindow: function(paddingLeft, clientWidth)
919 this._workingArea = clientWidth - WebInspector.TimelineCalculator._minWidth - paddingLeft;
920 this._paddingLeft = paddingLeft;
924 * @param {number} value
925 * @param {number=} precision
928 formatTime: function(value, precision)
930 return Number.preciseMillisToString(value - this.zeroTime(), precision);
936 maximumBoundary: function()
938 return this._maximumBoundary;
944 minimumBoundary: function()
946 return this._minimumBoundary;
954 return this._model.minimumRecordTime();
960 boundarySpan: function()
962 return this._maximumBoundary - this._minimumBoundary;
968 * @param {!WebInspector.Linkifier} linkifier
969 * @param {function(!WebInspector.TimelinePresentationModel.Record)} selectRecord
970 * @param {function()} scheduleRefresh
972 WebInspector.TimelineRecordListRow = function(linkifier, selectRecord, scheduleRefresh)
974 this.element = document.createElement("div");
975 this.element.row = this;
976 this.element.style.cursor = "pointer";
977 this.element.addEventListener("click", this._onClick.bind(this), false);
978 this.element.addEventListener("mouseover", this._onMouseOver.bind(this), false);
979 this.element.addEventListener("mouseout", this._onMouseOut.bind(this), false);
980 this._linkifier = linkifier;
982 // Warning is float right block, it goes first.
983 this._warningElement = this.element.createChild("div", "timeline-tree-item-warning hidden");
985 this._expandArrowElement = this.element.createChild("div", "timeline-tree-item-expand-arrow");
986 this._expandArrowElement.addEventListener("click", this._onExpandClick.bind(this), false);
987 var iconElement = this.element.createChild("span", "timeline-tree-icon");
988 this._typeElement = this.element.createChild("span", "type");
990 this._dataElement = this.element.createChild("span", "data dimmed");
991 this._scheduleRefresh = scheduleRefresh;
992 this._selectRecord = selectRecord;
995 WebInspector.TimelineRecordListRow.prototype = {
997 * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
998 * @param {number} offset
1000 update: function(presentationRecord, offset)
1002 this._record = presentationRecord;
1003 var record = presentationRecord.record();
1004 this._offset = offset;
1006 this.element.className = "timeline-tree-item timeline-category-" + record.category.name;
1007 var paddingLeft = 5;
1009 for (var currentRecord = presentationRecord.presentationParent() ? presentationRecord.presentationParent().presentationParent() : null; currentRecord; currentRecord = currentRecord.presentationParent())
1010 paddingLeft += 12 / (Math.max(1, step++));
1011 this.element.style.paddingLeft = paddingLeft + "px";
1013 this.element.classList.add("background");
1015 this._typeElement.textContent = record.title();
1017 if (this._dataElement.firstChild)
1018 this._dataElement.removeChildren();
1020 this._warningElement.classList.toggle("hidden", !presentationRecord.hasWarnings() && !presentationRecord.childHasWarnings());
1021 this._warningElement.classList.toggle("timeline-tree-item-child-warning", presentationRecord.childHasWarnings() && !presentationRecord.hasWarnings());
1023 if (presentationRecord.coalesced()) {
1024 this._dataElement.createTextChild(WebInspector.UIString("× %d", presentationRecord.presentationChildren().length));
1026 var detailsNode = WebInspector.TimelineUIUtils.buildDetailsNode(record, this._linkifier);
1028 this._dataElement.appendChild(document.createTextNode("("));
1029 this._dataElement.appendChild(detailsNode);
1030 this._dataElement.appendChild(document.createTextNode(")"));
1034 this._expandArrowElement.classList.toggle("parent", presentationRecord.expandable());
1035 this._expandArrowElement.classList.toggle("expanded", !!presentationRecord.visibleChildrenCount());
1036 this._record.setListRow(this);
1039 highlight: function(regExp, domChanges)
1041 var matchInfo = this.element.textContent.match(regExp);
1043 WebInspector.highlightSearchResult(this.element, matchInfo.index, matchInfo[0].length, domChanges);
1048 this.element.remove();
1052 * @param {!Event} event
1054 _onExpandClick: function(event)
1056 this._record.setCollapsed(!this._record.collapsed());
1057 this._scheduleRefresh();
1058 event.consume(true);
1062 * @param {?Event} event
1064 _onClick: function(event)
1066 this._selectRecord(this._record);
1070 * @param {boolean} selected
1072 renderAsSelected: function(selected)
1074 this.element.classList.toggle("selected", selected);
1078 * @param {?Event} event
1080 _onMouseOver: function(event)
1082 this.element.classList.add("hovered");
1083 if (this._record.graphRow())
1084 this._record.graphRow().element.classList.add("hovered");
1088 * @param {?Event} event
1090 _onMouseOut: function(event)
1092 this.element.classList.remove("hovered");
1093 if (this._record.graphRow())
1094 this._record.graphRow().element.classList.remove("hovered");
1100 * @param {function(!WebInspector.TimelinePresentationModel.Record)} selectRecord
1101 * @param {function()} scheduleRefresh
1103 WebInspector.TimelineRecordGraphRow = function(graphContainer, selectRecord, scheduleRefresh)
1105 this.element = document.createElement("div");
1106 this.element.row = this;
1107 this.element.addEventListener("mouseover", this._onMouseOver.bind(this), false);
1108 this.element.addEventListener("mouseout", this._onMouseOut.bind(this), false);
1109 this.element.addEventListener("click", this._onClick.bind(this), false);
1111 this._barAreaElement = document.createElement("div");
1112 this._barAreaElement.className = "timeline-graph-bar-area";
1113 this.element.appendChild(this._barAreaElement);
1115 this._barWithChildrenElement = document.createElement("div");
1116 this._barWithChildrenElement.className = "timeline-graph-bar with-children";
1117 this._barWithChildrenElement.row = this;
1118 this._barAreaElement.appendChild(this._barWithChildrenElement);
1120 this._barCpuElement = document.createElement("div");
1121 this._barCpuElement.className = "timeline-graph-bar cpu"
1122 this._barCpuElement.row = this;
1123 this._barAreaElement.appendChild(this._barCpuElement);
1125 this._barElement = document.createElement("div");
1126 this._barElement.className = "timeline-graph-bar";
1127 this._barElement.row = this;
1128 this._barAreaElement.appendChild(this._barElement);
1130 this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer);
1132 this._selectRecord = selectRecord;
1133 this._scheduleRefresh = scheduleRefresh;
1136 WebInspector.TimelineRecordGraphRow.prototype = {
1138 * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
1139 * @param {!WebInspector.TimelineCalculator} calculator
1140 * @param {number} expandOffset
1141 * @param {number} index
1143 update: function(presentationRecord, calculator, expandOffset, index)
1145 this._record = presentationRecord;
1146 var record = presentationRecord.record();
1147 this.element.className = "timeline-graph-side timeline-category-" + record.category.name;
1149 this.element.classList.add("background");
1151 var barPosition = calculator.computeBarGraphWindowPosition(record);
1152 this._barWithChildrenElement.style.left = barPosition.left + "px";
1153 this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px";
1154 this._barElement.style.left = barPosition.left + "px";
1155 this._barElement.style.width = (presentationRecord.coalesced() ? barPosition.widthWithChildren : barPosition.width) + "px";
1156 this._barCpuElement.style.left = barPosition.left + "px";
1157 this._barCpuElement.style.width = (presentationRecord.coalesced() ? barPosition.widthWithChildren : barPosition.cpuWidth) + "px";
1158 this._expandElement._update(presentationRecord, index, barPosition.left - expandOffset, barPosition.width);
1159 this._record.setGraphRow(this);
1163 * @param {?Event} event
1165 _onClick: function(event)
1167 // check if we click arrow and expand if yes.
1168 if (this._expandElement._arrow.containsEventPoint(event))
1170 this._selectRecord(this._record);
1174 * @param {boolean} selected
1176 renderAsSelected: function(selected)
1178 this.element.classList.toggle("selected", selected);
1183 this._record.setCollapsed(!this._record.collapsed());
1184 this._scheduleRefresh();
1188 * @param {?Event} event
1190 _onMouseOver: function(event)
1192 this.element.classList.add("hovered");
1193 if (this._record.listRow())
1194 this._record.listRow().element.classList.add("hovered");
1198 * @param {?Event} event
1200 _onMouseOut: function(event)
1202 this.element.classList.remove("hovered");
1203 if (this._record.listRow())
1204 this._record.listRow().element.classList.remove("hovered");
1209 this.element.remove();
1210 this._expandElement._dispose();
1217 WebInspector.TimelineExpandableElement = function(container)
1219 this._element = container.createChild("div", "timeline-expandable");
1220 this._element.createChild("div", "timeline-expandable-left");
1221 this._arrow = this._element.createChild("div", "timeline-expandable-arrow");
1224 WebInspector.TimelineExpandableElement.prototype = {
1226 * @param {!WebInspector.TimelinePresentationModel.Record} record
1228 _update: function(record, index, left, width)
1230 const rowHeight = WebInspector.TimelinePanel.rowHeight;
1231 if (record.visibleChildrenCount() || record.expandable()) {
1232 this._element.style.top = index * rowHeight + "px";
1233 this._element.style.left = left + "px";
1234 this._element.style.width = Math.max(12, width + 25) + "px";
1235 if (!record.collapsed()) {
1236 this._element.style.height = (record.visibleChildrenCount() + 1) * rowHeight + "px";
1237 this._element.classList.add("timeline-expandable-expanded");
1238 this._element.classList.remove("timeline-expandable-collapsed");
1240 this._element.style.height = rowHeight + "px";
1241 this._element.classList.add("timeline-expandable-collapsed");
1242 this._element.classList.remove("timeline-expandable-expanded");
1244 this._element.classList.remove("hidden");
1246 this._element.classList.add("hidden");
1249 _dispose: function()
1251 this._element.remove();