blob: 3e3f6736ec6dd3a40efb4924e8a77a632fd1c35f [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371/**
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @interface
33 */
34PerfUI.FlameChartDelegate = function() {};
35
36PerfUI.FlameChartDelegate.prototype = {
37 /**
38 * @param {number} startTime
39 * @param {number} endTime
40 * @param {boolean} animate
41 */
Alexei Filippov2578eb02018-04-11 08:15:0542 windowChanged(startTime, endTime, animate) {},
Blink Reformat4c46d092018-04-07 15:32:3743
44 /**
45 * @param {number} startTime
46 * @param {number} endTime
47 */
48 updateRangeSelection(startTime, endTime) {},
49
50 /**
51 * @param {!PerfUI.FlameChart} flameChart
52 * @param {?PerfUI.FlameChart.Group} group
53 */
54 updateSelectedGroup(flameChart, group) {},
55};
56
57/**
58 * @unrestricted
59 * @implements {PerfUI.TimelineGrid.Calculator}
60 * @implements {PerfUI.ChartViewportDelegate}
61 */
62PerfUI.FlameChart = class extends UI.VBox {
63 /**
64 * @param {!PerfUI.FlameChartDataProvider} dataProvider
65 * @param {!PerfUI.FlameChartDelegate} flameChartDelegate
66 * @param {!Common.Setting=} groupExpansionSetting
67 */
68 constructor(dataProvider, flameChartDelegate, groupExpansionSetting) {
69 super(true);
70 this.registerRequiredCSS('perf_ui/flameChart.css');
71 this.contentElement.classList.add('flame-chart-main-pane');
72 this._groupExpansionSetting = groupExpansionSetting;
73 this._groupExpansionState = groupExpansionSetting && groupExpansionSetting.get() || {};
74 this._flameChartDelegate = flameChartDelegate;
75
Alexei Filippov57ccafb2018-08-14 20:59:0576 this._useWebGL = Runtime.experiments.isEnabled('timelineWebGL');
Blink Reformat4c46d092018-04-07 15:32:3777 this._chartViewport = new PerfUI.ChartViewport(this);
78 this._chartViewport.show(this.contentElement);
79
80 this._dataProvider = dataProvider;
81
82 this._viewportElement = this._chartViewport.viewportElement;
Alexei Filippov57ccafb2018-08-14 20:59:0583 if (this._useWebGL) {
84 this._canvasGL = /** @type {!HTMLCanvasElement} */ (this._viewportElement.createChild('canvas', 'fill'));
85 this._initWebGL();
86 }
87 this._canvas = /** @type {!HTMLCanvasElement} */ (this._viewportElement.createChild('canvas', 'fill'));
Blink Reformat4c46d092018-04-07 15:32:3788
Joel Einbinder83fc76e2018-06-11 23:19:4789 this._canvas.tabIndex = 0;
Anubha Mathur72dd5822019-06-13 23:05:1990 UI.ARIAUtils.setAccessibleName(this._canvas, ls`Flame Chart`);
91 UI.ARIAUtils.markAsTree(this._canvas);
Blink Reformat4c46d092018-04-07 15:32:3792 this.setDefaultFocusedElement(this._canvas);
Anubha Mathur72dd5822019-06-13 23:05:1993 this._canvas.classList.add('flame-chart-canvas');
Blink Reformat4c46d092018-04-07 15:32:3794 this._canvas.addEventListener('mousemove', this._onMouseMove.bind(this), false);
95 this._canvas.addEventListener('mouseout', this._onMouseOut.bind(this), false);
96 this._canvas.addEventListener('click', this._onClick.bind(this), false);
97 this._canvas.addEventListener('keydown', this._onKeyDown.bind(this), false);
98
99 this._entryInfo = this._viewportElement.createChild('div', 'flame-chart-entry-info');
100 this._markerHighlighElement = this._viewportElement.createChild('div', 'flame-chart-marker-highlight-element');
101 this._highlightElement = this._viewportElement.createChild('div', 'flame-chart-highlight-element');
102 this._selectedElement = this._viewportElement.createChild('div', 'flame-chart-selected-element');
103
104 UI.installDragHandle(
105 this._viewportElement, this._startDragging.bind(this), this._dragging.bind(this), this._endDragging.bind(this),
106 null);
107
108 this._rulerEnabled = true;
109 this._rangeSelectionStart = 0;
110 this._rangeSelectionEnd = 0;
111 this._barHeight = 17;
112 this._textBaseline = 5;
113 this._textPadding = 5;
114 this._markerRadius = 6;
115 this._chartViewport.setWindowTimes(
116 dataProvider.minimumBoundary(), dataProvider.minimumBoundary() + dataProvider.totalTime());
117
118 /** @const */
119 this._headerLeftPadding = 6;
120 /** @const */
121 this._arrowSide = 8;
122 /** @const */
123 this._expansionArrowIndent = this._headerLeftPadding + this._arrowSide / 2;
124 /** @const */
125 this._headerLabelXPadding = 3;
126 /** @const */
127 this._headerLabelYPadding = 2;
128
129 this._highlightedMarkerIndex = -1;
130 this._highlightedEntryIndex = -1;
131 this._selectedEntryIndex = -1;
132 this._rawTimelineDataLength = 0;
Alexei Filippov72d792d2018-11-06 07:15:04133 /** @type {!Map<string, !Map<string,number>>} */
Blink Reformat4c46d092018-04-07 15:32:37134 this._textWidth = new Map();
Alexei Filippov6c622e92018-11-10 02:13:59135 /** @type {!Map<number, !{x: number, width: number}>} */
136 this._markerPositions = new Map();
Blink Reformat4c46d092018-04-07 15:32:37137
138 this._lastMouseOffsetX = 0;
139 this._selectedGroup = -1;
Anubha Mathur72dd5822019-06-13 23:05:19140
141 // Keyboard focused group is used to navigate groups irrespective of whether they are selectable or not
142 this._keyboardFocusedGroup = -1;
143
Blink Reformat4c46d092018-04-07 15:32:37144 this._selectedGroupBackroundColor = UI.themeSupport.patchColorText(
145 PerfUI.FlameChart.Colors.SelectedGroupBackground, UI.ThemeSupport.ColorUsage.Background);
146 this._selectedGroupBorderColor = UI.themeSupport.patchColorText(
147 PerfUI.FlameChart.Colors.SelectedGroupBorder, UI.ThemeSupport.ColorUsage.Background);
148 }
149
150 /**
151 * @override
152 */
153 willHide() {
154 this.hideHighlight();
155 }
156
157 /**
158 * @param {number} value
159 */
160 setBarHeight(value) {
161 this._barHeight = value;
162 }
163
164 /**
165 * @param {number} value
166 */
167 setTextBaseline(value) {
168 this._textBaseline = value;
169 }
170
171 /**
172 * @param {number} value
173 */
174 setTextPadding(value) {
175 this._textPadding = value;
176 }
177
178 /**
179 * @param {boolean} enable
180 */
181 enableRuler(enable) {
182 this._rulerEnabled = enable;
183 }
184
185 alwaysShowVerticalScroll() {
186 this._chartViewport.alwaysShowVerticalScroll();
187 }
188
189 disableRangeSelection() {
190 this._chartViewport.disableRangeSelection();
191 }
192
193 /**
194 * @param {number} entryIndex
195 */
196 highlightEntry(entryIndex) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34197 if (this._highlightedEntryIndex === entryIndex) {
Blink Reformat4c46d092018-04-07 15:32:37198 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34199 }
200 if (!this._dataProvider.entryColor(entryIndex)) {
Blink Reformat4c46d092018-04-07 15:32:37201 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34202 }
Blink Reformat4c46d092018-04-07 15:32:37203 this._highlightedEntryIndex = entryIndex;
204 this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
205 this.dispatchEventToListeners(PerfUI.FlameChart.Events.EntryHighlighted, entryIndex);
206 }
207
208 hideHighlight() {
209 this._entryInfo.removeChildren();
210 this._highlightedEntryIndex = -1;
211 this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
212 this.dispatchEventToListeners(PerfUI.FlameChart.Events.EntryHighlighted, -1);
213 }
214
215 _resetCanvas() {
216 const ratio = window.devicePixelRatio;
217 const width = Math.round(this._offsetWidth * ratio);
218 const height = Math.round(this._offsetHeight * ratio);
219 this._canvas.width = width;
220 this._canvas.height = height;
221 this._canvas.style.width = `${width / ratio}px`;
222 this._canvas.style.height = `${height / ratio}px`;
Alexei Filippov57ccafb2018-08-14 20:59:05223 if (this._useWebGL) {
224 this._canvasGL.width = width;
225 this._canvasGL.height = height;
226 this._canvasGL.style.width = `${width / ratio}px`;
227 this._canvasGL.style.height = `${height / ratio}px`;
228 }
Blink Reformat4c46d092018-04-07 15:32:37229 }
230
231 /**
232 * @override
233 * @param {number} startTime
234 * @param {number} endTime
235 * @param {boolean} animate
236 */
Alexei Filippov2578eb02018-04-11 08:15:05237 windowChanged(startTime, endTime, animate) {
238 this._flameChartDelegate.windowChanged(startTime, endTime, animate);
Blink Reformat4c46d092018-04-07 15:32:37239 }
240
241 /**
242 * @override
243 * @param {number} startTime
244 * @param {number} endTime
245 */
246 updateRangeSelection(startTime, endTime) {
247 this._flameChartDelegate.updateRangeSelection(startTime, endTime);
248 }
249
250 /**
251 * @override
252 * @param {number} width
253 * @param {number} height
254 */
255 setSize(width, height) {
256 this._offsetWidth = width;
257 this._offsetHeight = height;
258 }
259
260 /**
261 * @param {!MouseEvent} event
262 */
263 _startDragging(event) {
264 this.hideHighlight();
265 this._maxDragOffset = 0;
266 this._dragStartX = event.pageX;
267 this._dragStartY = event.pageY;
268 return true;
269 }
270
271 /**
272 * @param {!MouseEvent} event
273 */
274 _dragging(event) {
275 const dx = event.pageX - this._dragStartX;
276 const dy = event.pageY - this._dragStartY;
277 this._maxDragOffset = Math.max(this._maxDragOffset, Math.sqrt(dx * dx + dy * dy));
278 }
279
280 /**
281 * @param {!MouseEvent} event
282 */
283 _endDragging(event) {
284 this._updateHighlight();
285 }
286
287 /**
288 * @return {?PerfUI.FlameChart.TimelineData}
289 */
290 _timelineData() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34291 if (!this._dataProvider) {
Blink Reformat4c46d092018-04-07 15:32:37292 return null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34293 }
Blink Reformat4c46d092018-04-07 15:32:37294 const timelineData = this._dataProvider.timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:34295 if (timelineData !== this._rawTimelineData || timelineData.entryStartTimes.length !== this._rawTimelineDataLength) {
Blink Reformat4c46d092018-04-07 15:32:37296 this._processTimelineData(timelineData);
Tim van der Lippe1d6e57a2019-09-30 11:55:34297 }
Blink Reformat4c46d092018-04-07 15:32:37298 return this._rawTimelineData;
299 }
300
301 /**
302 * @param {number} entryIndex
303 */
304 _revealEntry(entryIndex) {
305 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:34306 if (!timelineData) {
Blink Reformat4c46d092018-04-07 15:32:37307 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34308 }
Blink Reformat4c46d092018-04-07 15:32:37309 const timeLeft = this._chartViewport.windowLeftTime();
310 const timeRight = this._chartViewport.windowRightTime();
311 const entryStartTime = timelineData.entryStartTimes[entryIndex];
312 const entryTotalTime = timelineData.entryTotalTimes[entryIndex];
313 const entryEndTime = entryStartTime + entryTotalTime;
314 let minEntryTimeWindow = Math.min(entryTotalTime, timeRight - timeLeft);
315
316 const level = timelineData.entryLevels[entryIndex];
317 this._chartViewport.setScrollOffset(this._levelToOffset(level), this._levelHeight(level));
318
319 const minVisibleWidthPx = 30;
320 const futurePixelToTime = (timeRight - timeLeft) / this._offsetWidth;
321 minEntryTimeWindow = Math.max(minEntryTimeWindow, futurePixelToTime * minVisibleWidthPx);
322 if (timeLeft > entryEndTime) {
323 const delta = timeLeft - entryEndTime + minEntryTimeWindow;
Alexei Filippov2578eb02018-04-11 08:15:05324 this.windowChanged(timeLeft - delta, timeRight - delta, /* animate */ true);
Blink Reformat4c46d092018-04-07 15:32:37325 } else if (timeRight < entryStartTime) {
326 const delta = entryStartTime - timeRight + minEntryTimeWindow;
Alexei Filippov2578eb02018-04-11 08:15:05327 this.windowChanged(timeLeft + delta, timeRight + delta, /* animate */ true);
Blink Reformat4c46d092018-04-07 15:32:37328 }
329 }
330
331 /**
332 * @param {number} startTime
333 * @param {number} endTime
334 * @param {boolean=} animate
335 */
336 setWindowTimes(startTime, endTime, animate) {
337 this._chartViewport.setWindowTimes(startTime, endTime, animate);
338 this._updateHighlight();
339 }
340
341 /**
342 * @param {!Event} event
343 */
344 _onMouseMove(event) {
345 this._lastMouseOffsetX = event.offsetX;
346 this._lastMouseOffsetY = event.offsetY;
Tim van der Lippe1d6e57a2019-09-30 11:55:34347 if (!this._enabled()) {
Blink Reformat4c46d092018-04-07 15:32:37348 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34349 }
350 if (this._chartViewport.isDragging()) {
Blink Reformat4c46d092018-04-07 15:32:37351 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34352 }
Blink Reformat4c46d092018-04-07 15:32:37353 if (this._coordinatesToGroupIndex(event.offsetX, event.offsetY, true /* headerOnly */) >= 0) {
354 this.hideHighlight();
355 this._viewportElement.style.cursor = 'pointer';
356 return;
357 }
358 this._updateHighlight();
359 }
360
361 _updateHighlight() {
Alexei Filippov8ee66382018-11-30 01:53:56362 const entryIndex = this._coordinatesToEntryIndex(this._lastMouseOffsetX, this._lastMouseOffsetY);
Blink Reformat4c46d092018-04-07 15:32:37363 if (entryIndex === -1) {
364 this.hideHighlight();
365 const group =
366 this._coordinatesToGroupIndex(this._lastMouseOffsetX, this._lastMouseOffsetY, false /* headerOnly */);
Tim van der Lippe1d6e57a2019-09-30 11:55:34367 if (group >= 0 && this._rawTimelineData.groups[group].selectable) {
Blink Reformat4c46d092018-04-07 15:32:37368 this._viewportElement.style.cursor = 'pointer';
Tim van der Lippe1d6e57a2019-09-30 11:55:34369 } else {
Blink Reformat4c46d092018-04-07 15:32:37370 this._viewportElement.style.cursor = 'default';
Tim van der Lippe1d6e57a2019-09-30 11:55:34371 }
Blink Reformat4c46d092018-04-07 15:32:37372 return;
373 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34374 if (this._chartViewport.isDragging()) {
Blink Reformat4c46d092018-04-07 15:32:37375 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34376 }
Blink Reformat4c46d092018-04-07 15:32:37377 this._updatePopover(entryIndex);
378 this._viewportElement.style.cursor = this._dataProvider.canJumpToEntry(entryIndex) ? 'pointer' : 'default';
379 this.highlightEntry(entryIndex);
380 }
381
382 _onMouseOut() {
383 this._lastMouseOffsetX = -1;
384 this._lastMouseOffsetY = -1;
385 this.hideHighlight();
386 }
387
388 /**
389 * @param {number} entryIndex
390 */
391 _updatePopover(entryIndex) {
392 if (entryIndex === this._highlightedEntryIndex) {
393 this._updatePopoverOffset();
394 return;
395 }
396 this._entryInfo.removeChildren();
397 const popoverElement = this._dataProvider.prepareHighlightedEntryInfo(entryIndex);
398 if (popoverElement) {
399 this._entryInfo.appendChild(popoverElement);
400 this._updatePopoverOffset();
401 }
402 }
403
404 _updatePopoverOffset() {
405 const mouseX = this._lastMouseOffsetX;
406 const mouseY = this._lastMouseOffsetY;
407 const parentWidth = this._entryInfo.parentElement.clientWidth;
408 const parentHeight = this._entryInfo.parentElement.clientHeight;
409 const infoWidth = this._entryInfo.clientWidth;
410 const infoHeight = this._entryInfo.clientHeight;
411 const /** @const */ offsetX = 10;
412 const /** @const */ offsetY = 6;
413 let x;
414 let y;
415 for (let quadrant = 0; quadrant < 4; ++quadrant) {
416 const dx = quadrant & 2 ? -offsetX - infoWidth : offsetX;
417 const dy = quadrant & 1 ? -offsetY - infoHeight : offsetY;
418 x = Number.constrain(mouseX + dx, 0, parentWidth - infoWidth);
419 y = Number.constrain(mouseY + dy, 0, parentHeight - infoHeight);
Tim van der Lippe1d6e57a2019-09-30 11:55:34420 if (x >= mouseX || mouseX >= x + infoWidth || y >= mouseY || mouseY >= y + infoHeight) {
Blink Reformat4c46d092018-04-07 15:32:37421 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:34422 }
Blink Reformat4c46d092018-04-07 15:32:37423 }
424 this._entryInfo.style.left = x + 'px';
425 this._entryInfo.style.top = y + 'px';
426 }
427
428 /**
429 * @param {!Event} event
430 */
431 _onClick(event) {
432 this.focus();
433 // onClick comes after dragStart and dragEnd events.
434 // So if there was drag (mouse move) in the middle of that events
435 // we skip the click. Otherwise we jump to the sources.
436 const /** @const */ clickThreshold = 5;
Tim van der Lippe1d6e57a2019-09-30 11:55:34437 if (this._maxDragOffset > clickThreshold) {
Blink Reformat4c46d092018-04-07 15:32:37438 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34439 }
Blink Reformat4c46d092018-04-07 15:32:37440
441 this._selectGroup(this._coordinatesToGroupIndex(event.offsetX, event.offsetY, false /* headerOnly */));
Anubha Mathur72dd5822019-06-13 23:05:19442 this._toggleGroupExpand(this._coordinatesToGroupIndex(event.offsetX, event.offsetY, true /* headerOnly */));
Blink Reformat4c46d092018-04-07 15:32:37443 const timelineData = this._timelineData();
444 if (event.shiftKey && this._highlightedEntryIndex !== -1 && timelineData) {
445 const start = timelineData.entryStartTimes[this._highlightedEntryIndex];
446 const end = start + timelineData.entryTotalTimes[this._highlightedEntryIndex];
447 this._chartViewport.setRangeSelection(start, end);
448 } else {
449 this._chartViewport.onClick(event);
450 this.dispatchEventToListeners(PerfUI.FlameChart.Events.EntrySelected, this._highlightedEntryIndex);
451 }
452 }
453
454 /**
455 * @param {number} groupIndex
456 */
457 _selectGroup(groupIndex) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34458 if (groupIndex < 0 || this._selectedGroup === groupIndex) {
Blink Reformat4c46d092018-04-07 15:32:37459 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34460 }
Anubha Mathur72dd5822019-06-13 23:05:19461 const groups = this._rawTimelineData.groups;
462 this._keyboardFocusedGroup = groupIndex;
463 if (!groups[groupIndex].selectable) {
464 this._deselectAllGroups();
465 } else {
466 this._selectedGroup = groupIndex;
467 this._flameChartDelegate.updateSelectedGroup(this, groups[groupIndex]);
468 this._resetCanvas();
469 this._draw();
Michael Liao (WPT)b54514b2019-08-16 23:11:53470
471 const groupName = groups[groupIndex].name;
472 UI.ARIAUtils.alert(ls`${groupName} selected`, this._canvas);
Anubha Mathur72dd5822019-06-13 23:05:19473 }
474 }
Blink Reformat4c46d092018-04-07 15:32:37475
Anubha Mathur72dd5822019-06-13 23:05:19476 _deselectAllGroups() {
477 this._selectedGroup = -1;
478 this._flameChartDelegate.updateSelectedGroup(this, null);
479 this._resetCanvas();
480 this._draw();
481 }
482
483 _deselectAllEntries() {
484 this._selectedEntryIndex = -1;
Blink Reformat4c46d092018-04-07 15:32:37485 this._resetCanvas();
486 this._draw();
487 }
488
489 /**
Anubha Mathur72dd5822019-06-13 23:05:19490 * @param {number} index
491 */
492 _isGroupFocused(index) {
493 return index === this._selectedGroup || index === this._keyboardFocusedGroup;
494 }
495
496 /**
Blink Reformat4c46d092018-04-07 15:32:37497 * @param {number} groupIndex
498 */
Anubha Mathur72dd5822019-06-13 23:05:19499 _toggleGroupExpand(groupIndex) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34500 if (groupIndex < 0 || !this._isGroupCollapsible(groupIndex)) {
Anubha Mathur72dd5822019-06-13 23:05:19501 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34502 }
Anubha Mathur72dd5822019-06-13 23:05:19503
504 this._expandGroup(groupIndex, !this._rawTimelineData.groups[groupIndex].expanded /* setExpanded */);
505 }
506
507 /**
508 * @param {number} groupIndex
509 * @param {boolean=} setExpanded
Michael Liao (WPT)b54514b2019-08-16 23:11:53510 * @param {boolean=} propagatedExpand
Anubha Mathur72dd5822019-06-13 23:05:19511 */
Michael Liao (WPT)b54514b2019-08-16 23:11:53512 _expandGroup(groupIndex, setExpanded = true, propagatedExpand = false) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34513 if (groupIndex < 0 || !this._isGroupCollapsible(groupIndex)) {
Blink Reformat4c46d092018-04-07 15:32:37514 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34515 }
Blink Reformat4c46d092018-04-07 15:32:37516
517 const groups = this._rawTimelineData.groups;
518 const group = groups[groupIndex];
Anubha Mathur72dd5822019-06-13 23:05:19519 group.expanded = setExpanded;
520
Blink Reformat4c46d092018-04-07 15:32:37521 this._groupExpansionState[group.name] = group.expanded;
Tim van der Lippe1d6e57a2019-09-30 11:55:34522 if (this._groupExpansionSetting) {
Blink Reformat4c46d092018-04-07 15:32:37523 this._groupExpansionSetting.set(this._groupExpansionState);
Tim van der Lippe1d6e57a2019-09-30 11:55:34524 }
Blink Reformat4c46d092018-04-07 15:32:37525 this._updateLevelPositions();
526
527 this._updateHighlight();
528 if (!group.expanded) {
529 const timelineData = this._timelineData();
530 const level = timelineData.entryLevels[this._selectedEntryIndex];
531 if (this._selectedEntryIndex >= 0 && level >= group.startLevel &&
Tim van der Lippe1d6e57a2019-09-30 11:55:34532 (groupIndex >= groups.length - 1 || groups[groupIndex + 1].startLevel > level)) {
Blink Reformat4c46d092018-04-07 15:32:37533 this._selectedEntryIndex = -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34534 }
Blink Reformat4c46d092018-04-07 15:32:37535 }
536
537 this._updateHeight();
538 this._resetCanvas();
539 this._draw();
Michael Liao (WPT)b54514b2019-08-16 23:11:53540
541 // We only want to read expanded/collapsed state on user inputted expand/collapse
542 if (!propagatedExpand) {
543 const groupName = groups[groupIndex].name;
544 const content = group.expanded ? ls`${groupName} expanded` : ls`${groupName} collapsed`;
545 UI.ARIAUtils.alert(content, this._canvas);
546 }
Blink Reformat4c46d092018-04-07 15:32:37547 }
548
549 /**
550 * @param {!Event} e
551 */
552 _onKeyDown(e) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34553 if (!UI.KeyboardShortcut.hasNoModifiers(e) || !this._timelineData()) {
Anubha Mathur72dd5822019-06-13 23:05:19554 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34555 }
Anubha Mathur72dd5822019-06-13 23:05:19556
557 const eventHandled = this._handleSelectionNavigation(e);
558
559 // Handle keyboard navigation in groups
Tim van der Lippe1d6e57a2019-09-30 11:55:34560 if (!eventHandled && this._rawTimelineData && this._rawTimelineData.groups) {
Anubha Mathur72dd5822019-06-13 23:05:19561 this._handleKeyboardGroupNavigation(e);
Tim van der Lippe1d6e57a2019-09-30 11:55:34562 }
Blink Reformat4c46d092018-04-07 15:32:37563 }
564
565 /**
566 * @param {!Event} e
567 */
Anubha Mathur72dd5822019-06-13 23:05:19568 _handleKeyboardGroupNavigation(e) {
569 let handled = false;
570 let entrySelected = false;
571
572 if (e.code === 'ArrowUp') {
573 handled = this._selectPreviousGroup();
574 } else if (e.code === 'ArrowDown') {
575 handled = this._selectNextGroup();
576 } else if (e.code === 'ArrowLeft') {
577 if (this._keyboardFocusedGroup >= 0) {
578 this._expandGroup(this._keyboardFocusedGroup, false /* setExpanded */);
579 handled = true;
580 }
581 } else if (e.code === 'ArrowRight') {
582 if (this._keyboardFocusedGroup >= 0) {
583 this._expandGroup(this._keyboardFocusedGroup, true /* setExpanded */);
584 this._selectFirstChild();
585 handled = true;
586 }
587 } else if (isEnterKey(e)) {
588 entrySelected = this._selectFirstEntryInCurrentGroup();
589 handled = entrySelected;
590 }
591
Tim van der Lippe1d6e57a2019-09-30 11:55:34592 if (handled && !entrySelected) {
Anubha Mathur72dd5822019-06-13 23:05:19593 this._deselectAllEntries();
Tim van der Lippe1d6e57a2019-09-30 11:55:34594 }
Anubha Mathur72dd5822019-06-13 23:05:19595
Tim van der Lippe1d6e57a2019-09-30 11:55:34596 if (handled) {
Anubha Mathur72dd5822019-06-13 23:05:19597 e.consume(true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34598 }
Anubha Mathur72dd5822019-06-13 23:05:19599 }
600
601 /**
602 * @return {boolean}
603 */
604 _selectFirstEntryInCurrentGroup() {
605 const allGroups = this._rawTimelineData.groups;
606
Tim van der Lippe1d6e57a2019-09-30 11:55:34607 if (this._keyboardFocusedGroup < 0) {
Anubha Mathur72dd5822019-06-13 23:05:19608 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34609 }
Anubha Mathur72dd5822019-06-13 23:05:19610
611 const group = allGroups[this._keyboardFocusedGroup];
612 const startLevelInGroup = group.startLevel;
613
614 // Return if no levels in this group
Tim van der Lippe1d6e57a2019-09-30 11:55:34615 if (startLevelInGroup < 0) {
Anubha Mathur72dd5822019-06-13 23:05:19616 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34617 }
Anubha Mathur72dd5822019-06-13 23:05:19618
619 // Make sure this is the innermost nested group with this startLevel
620 // This is because a parent group also contains levels of all its child groups
621 // So check if the next group has the same level, if it does, user should
622 // go to that child group to select this entry
623 if (this._keyboardFocusedGroup < allGroups.length - 1 &&
Tim van der Lippe1d6e57a2019-09-30 11:55:34624 allGroups[this._keyboardFocusedGroup + 1].startLevel === startLevelInGroup) {
Anubha Mathur72dd5822019-06-13 23:05:19625 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34626 }
Anubha Mathur72dd5822019-06-13 23:05:19627
628
629 // Get first (default) entry in startLevel of selected group
630 const firstEntryIndex = this._timelineLevels[startLevelInGroup][0];
631
632 this._expandGroup(this._keyboardFocusedGroup, true /* setExpanded */);
633 this.setSelectedEntry(firstEntryIndex);
634 return true;
635 }
636
637 /**
638 * @return {boolean}
639 */
640 _selectPreviousGroup() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34641 if (this._keyboardFocusedGroup <= 0) {
Anubha Mathur72dd5822019-06-13 23:05:19642 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34643 }
Anubha Mathur72dd5822019-06-13 23:05:19644
645 const groupIndexToSelect = this._getGroupIndexToSelect(-1 /* offset */);
646 this._selectGroup(groupIndexToSelect);
647 return true;
648 }
649
650 /**
651 * @return {boolean}
652 */
653 _selectNextGroup() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34654 if (this._keyboardFocusedGroup >= this._rawTimelineData.groups.length - 1) {
Anubha Mathur72dd5822019-06-13 23:05:19655 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34656 }
Anubha Mathur72dd5822019-06-13 23:05:19657
658 const groupIndexToSelect = this._getGroupIndexToSelect(1 /* offset */);
659 this._selectGroup(groupIndexToSelect);
660 return true;
661 }
662
663 /**
664 * @param {number} offset
665 * @return {number}
666 */
667 _getGroupIndexToSelect(offset) {
668 const allGroups = this._rawTimelineData.groups;
669 let groupIndexToSelect = this._keyboardFocusedGroup;
670 let groupName, groupWithSubNestingLevel;
671
672 do {
673 groupIndexToSelect += offset;
674 groupName = this._rawTimelineData.groups[groupIndexToSelect].name;
675 groupWithSubNestingLevel = this._keyboardFocusedGroup !== -1 &&
676 allGroups[groupIndexToSelect].style.nestingLevel > allGroups[this._keyboardFocusedGroup].style.nestingLevel;
677 } while (groupIndexToSelect > 0 && groupIndexToSelect < allGroups.length - 1 &&
678 (!groupName || groupWithSubNestingLevel));
679
680 return groupIndexToSelect;
681 }
682
683 _selectFirstChild() {
684 const allGroups = this._rawTimelineData.groups;
Tim van der Lippe1d6e57a2019-09-30 11:55:34685 if (this._keyboardFocusedGroup < 0 || this._keyboardFocusedGroup >= allGroups.length - 1) {
Anubha Mathur72dd5822019-06-13 23:05:19686 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34687 }
Anubha Mathur72dd5822019-06-13 23:05:19688
689 const groupIndexToSelect = this._keyboardFocusedGroup + 1;
690 if (allGroups[groupIndexToSelect].style.nestingLevel > allGroups[this._keyboardFocusedGroup].style.nestingLevel) {
691 this._selectGroup(groupIndexToSelect);
Michael Liao (WPT)b54514b2019-08-16 23:11:53692 this._expandGroup(groupIndexToSelect, true /* setExpanded */, true /* propagatedExpand */);
Anubha Mathur72dd5822019-06-13 23:05:19693 }
694 }
695
696 /**
697 * @param {!Event} e
698 * @return {boolean}
699 */
Blink Reformat4c46d092018-04-07 15:32:37700 _handleSelectionNavigation(e) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34701 if (this._selectedEntryIndex === -1) {
Anubha Mathur72dd5822019-06-13 23:05:19702 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34703 }
Blink Reformat4c46d092018-04-07 15:32:37704 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:34705 if (!timelineData) {
Anubha Mathur72dd5822019-06-13 23:05:19706 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34707 }
Blink Reformat4c46d092018-04-07 15:32:37708
709 /**
710 * @param {number} time
711 * @param {number} entryIndex
712 * @return {number}
713 */
714 function timeComparator(time, entryIndex) {
715 return time - timelineData.entryStartTimes[entryIndex];
716 }
717
718 /**
719 * @param {number} entry1
720 * @param {number} entry2
721 * @return {boolean}
722 */
723 function entriesIntersect(entry1, entry2) {
724 const start1 = timelineData.entryStartTimes[entry1];
725 const start2 = timelineData.entryStartTimes[entry2];
726 const end1 = start1 + timelineData.entryTotalTimes[entry1];
727 const end2 = start2 + timelineData.entryTotalTimes[entry2];
728 return start1 < end2 && start2 < end1;
729 }
730
731 const keys = UI.KeyboardShortcut.Keys;
732 if (e.keyCode === keys.Left.code || e.keyCode === keys.Right.code) {
733 const level = timelineData.entryLevels[this._selectedEntryIndex];
734 const levelIndexes = this._timelineLevels[level];
735 let indexOnLevel = levelIndexes.lowerBound(this._selectedEntryIndex);
736 indexOnLevel += e.keyCode === keys.Left.code ? -1 : 1;
737 e.consume(true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34738 if (indexOnLevel >= 0 && indexOnLevel < levelIndexes.length) {
Blink Reformat4c46d092018-04-07 15:32:37739 this.dispatchEventToListeners(PerfUI.FlameChart.Events.EntrySelected, levelIndexes[indexOnLevel]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34740 }
Anubha Mathur72dd5822019-06-13 23:05:19741 return true;
Blink Reformat4c46d092018-04-07 15:32:37742 }
743 if (e.keyCode === keys.Up.code || e.keyCode === keys.Down.code) {
Blink Reformat4c46d092018-04-07 15:32:37744 let level = timelineData.entryLevels[this._selectedEntryIndex];
745 level += e.keyCode === keys.Up.code ? -1 : 1;
Anubha Mathur72dd5822019-06-13 23:05:19746 if (level < 0 || level >= this._timelineLevels.length) {
747 this._deselectAllEntries();
748 e.consume(true);
749 return true;
750 }
Blink Reformat4c46d092018-04-07 15:32:37751 const entryTime = timelineData.entryStartTimes[this._selectedEntryIndex] +
752 timelineData.entryTotalTimes[this._selectedEntryIndex] / 2;
753 const levelIndexes = this._timelineLevels[level];
754 let indexOnLevel = levelIndexes.upperBound(entryTime, timeComparator) - 1;
755 if (!entriesIntersect(this._selectedEntryIndex, levelIndexes[indexOnLevel])) {
756 ++indexOnLevel;
757 if (indexOnLevel >= levelIndexes.length ||
Anubha Mathur72dd5822019-06-13 23:05:19758 !entriesIntersect(this._selectedEntryIndex, levelIndexes[indexOnLevel])) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34759 if (e.code === 'ArrowDown') {
Anubha Mathur72dd5822019-06-13 23:05:19760 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34761 }
Anubha Mathur72dd5822019-06-13 23:05:19762
763 // Stay in the current group and give focus to the parent group instead of entries
764 this._deselectAllEntries();
765 e.consume(true);
766 return true;
767 }
Blink Reformat4c46d092018-04-07 15:32:37768 }
Anubha Mathur72dd5822019-06-13 23:05:19769 e.consume(true);
Blink Reformat4c46d092018-04-07 15:32:37770 this.dispatchEventToListeners(PerfUI.FlameChart.Events.EntrySelected, levelIndexes[indexOnLevel]);
Anubha Mathur72dd5822019-06-13 23:05:19771 return true;
Blink Reformat4c46d092018-04-07 15:32:37772 }
Anubha Mathur72dd5822019-06-13 23:05:19773 return false;
Blink Reformat4c46d092018-04-07 15:32:37774 }
775
776 /**
777 * @param {number} x
778 * @param {number} y
779 * @return {number}
780 */
781 _coordinatesToEntryIndex(x, y) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34782 if (x < 0 || y < 0) {
Blink Reformat4c46d092018-04-07 15:32:37783 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34784 }
Blink Reformat4c46d092018-04-07 15:32:37785 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:34786 if (!timelineData) {
Blink Reformat4c46d092018-04-07 15:32:37787 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34788 }
Blink Reformat4c46d092018-04-07 15:32:37789 y += this._chartViewport.scrollOffset();
790 const cursorLevel = this._visibleLevelOffsets.upperBound(y) - 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34791 if (cursorLevel < 0 || !this._visibleLevels[cursorLevel]) {
Blink Reformat4c46d092018-04-07 15:32:37792 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34793 }
Blink Reformat4c46d092018-04-07 15:32:37794 const offsetFromLevel = y - this._visibleLevelOffsets[cursorLevel];
Tim van der Lippe1d6e57a2019-09-30 11:55:34795 if (offsetFromLevel > this._levelHeight(cursorLevel)) {
Blink Reformat4c46d092018-04-07 15:32:37796 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34797 }
Alexei Filippov6c622e92018-11-10 02:13:59798
799 // Check markers first.
800 for (const [index, pos] of this._markerPositions) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34801 if (timelineData.entryLevels[index] !== cursorLevel) {
Alexei Filippov6c622e92018-11-10 02:13:59802 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34803 }
804 if (pos.x <= x && x < pos.x + pos.width) {
Alexei Filippov6c622e92018-11-10 02:13:59805 return /** @type {number} */ (index);
Tim van der Lippe1d6e57a2019-09-30 11:55:34806 }
Alexei Filippov6c622e92018-11-10 02:13:59807 }
808
809 // Check regular entries.
Blink Reformat4c46d092018-04-07 15:32:37810 const entryStartTimes = timelineData.entryStartTimes;
Alexei Filippov6c622e92018-11-10 02:13:59811 const entriesOnLevel = this._timelineLevels[cursorLevel];
Tim van der Lippe1d6e57a2019-09-30 11:55:34812 if (!entriesOnLevel || !entriesOnLevel.length) {
Blink Reformat4c46d092018-04-07 15:32:37813 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34814 }
Blink Reformat4c46d092018-04-07 15:32:37815
Blink Reformat4c46d092018-04-07 15:32:37816 const cursorTime = this._chartViewport.pixelToTime(x);
Alexei Filippov6c622e92018-11-10 02:13:59817 const indexOnLevel = Math.max(
818 entriesOnLevel.upperBound(cursorTime, (time, entryIndex) => time - entryStartTimes[entryIndex]) - 1, 0);
Blink Reformat4c46d092018-04-07 15:32:37819
820 /**
821 * @this {PerfUI.FlameChart}
Alexei Filippov72d792d2018-11-06 07:15:04822 * @param {number|undefined} entryIndex
Blink Reformat4c46d092018-04-07 15:32:37823 * @return {boolean}
824 */
825 function checkEntryHit(entryIndex) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34826 if (entryIndex === undefined) {
Blink Reformat4c46d092018-04-07 15:32:37827 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34828 }
Blink Reformat4c46d092018-04-07 15:32:37829 const startTime = entryStartTimes[entryIndex];
Alexei Filippov6c622e92018-11-10 02:13:59830 const duration = timelineData.entryTotalTimes[entryIndex];
Blink Reformat4c46d092018-04-07 15:32:37831 const startX = this._chartViewport.timeToPosition(startTime);
Alexei Filippov6c622e92018-11-10 02:13:59832 const endX = this._chartViewport.timeToPosition(startTime + duration);
833 const barThresholdPx = 3;
Blink Reformat4c46d092018-04-07 15:32:37834 return startX - barThresholdPx < x && x < endX + barThresholdPx;
835 }
836
Alexei Filippov6c622e92018-11-10 02:13:59837 let entryIndex = entriesOnLevel[indexOnLevel];
Tim van der Lippe1d6e57a2019-09-30 11:55:34838 if (checkEntryHit.call(this, entryIndex)) {
Blink Reformat4c46d092018-04-07 15:32:37839 return entryIndex;
Tim van der Lippe1d6e57a2019-09-30 11:55:34840 }
Alexei Filippov6c622e92018-11-10 02:13:59841 entryIndex = entriesOnLevel[indexOnLevel + 1];
Tim van der Lippe1d6e57a2019-09-30 11:55:34842 if (checkEntryHit.call(this, entryIndex)) {
Blink Reformat4c46d092018-04-07 15:32:37843 return entryIndex;
Tim van der Lippe1d6e57a2019-09-30 11:55:34844 }
Blink Reformat4c46d092018-04-07 15:32:37845 return -1;
846 }
847
848 /**
849 * @param {number} x
850 * @param {number} y
851 * @param {boolean} headerOnly
852 * @return {number}
853 */
854 _coordinatesToGroupIndex(x, y, headerOnly) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34855 if (x < 0 || y < 0) {
Blink Reformat4c46d092018-04-07 15:32:37856 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34857 }
Blink Reformat4c46d092018-04-07 15:32:37858 y += this._chartViewport.scrollOffset();
859 const groups = this._rawTimelineData.groups || [];
860 const group = this._groupOffsets.upperBound(y) - 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34861 if (group < 0 || group >= groups.length) {
Blink Reformat4c46d092018-04-07 15:32:37862 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34863 }
Blink Reformat4c46d092018-04-07 15:32:37864 const height = headerOnly ? groups[group].style.height : this._groupOffsets[group + 1] - this._groupOffsets[group];
Tim van der Lippe1d6e57a2019-09-30 11:55:34865 if (y - this._groupOffsets[group] >= height) {
Blink Reformat4c46d092018-04-07 15:32:37866 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34867 }
868 if (!headerOnly) {
Blink Reformat4c46d092018-04-07 15:32:37869 return group;
Tim van der Lippe1d6e57a2019-09-30 11:55:34870 }
Blink Reformat4c46d092018-04-07 15:32:37871
872 const context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
873 context.save();
874 context.font = groups[group].style.font;
875 const right = this._headerLeftPadding + this._labelWidthForGroup(context, groups[group]);
876 context.restore();
Tim van der Lippe1d6e57a2019-09-30 11:55:34877 if (x > right) {
Blink Reformat4c46d092018-04-07 15:32:37878 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34879 }
Blink Reformat4c46d092018-04-07 15:32:37880
881 return group;
882 }
883
884 /**
885 * @param {number} x
886 * @return {number}
887 */
888 _markerIndexAtPosition(x) {
889 const markers = this._timelineData().markers;
Tim van der Lippe1d6e57a2019-09-30 11:55:34890 if (!markers) {
Blink Reformat4c46d092018-04-07 15:32:37891 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34892 }
Blink Reformat4c46d092018-04-07 15:32:37893 const /** @const */ accurracyOffsetPx = 4;
894 const time = this._chartViewport.pixelToTime(x);
895 const leftTime = this._chartViewport.pixelToTime(x - accurracyOffsetPx);
896 const rightTime = this._chartViewport.pixelToTime(x + accurracyOffsetPx);
897 const left = this._markerIndexBeforeTime(leftTime);
898 let markerIndex = -1;
899 let distance = Infinity;
900 for (let i = left; i < markers.length && markers[i].startTime() < rightTime; i++) {
901 const nextDistance = Math.abs(markers[i].startTime() - time);
902 if (nextDistance < distance) {
903 markerIndex = i;
904 distance = nextDistance;
905 }
906 }
907 return markerIndex;
908 }
909
910 /**
911 * @param {number} time
912 * @return {number}
913 */
914 _markerIndexBeforeTime(time) {
915 return this._timelineData().markers.lowerBound(
916 time, (markerTimestamp, marker) => markerTimestamp - marker.startTime());
917 }
918
919 _draw() {
920 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:34921 if (!timelineData) {
Blink Reformat4c46d092018-04-07 15:32:37922 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34923 }
Blink Reformat4c46d092018-04-07 15:32:37924
925 const width = this._offsetWidth;
926 const height = this._offsetHeight;
927 const context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
928 context.save();
929 const ratio = window.devicePixelRatio;
930 const top = this._chartViewport.scrollOffset();
931 context.scale(ratio, ratio);
Alexei Filippov57ccafb2018-08-14 20:59:05932 context.fillStyle = 'rgba(0, 0, 0, 0)';
933 context.fillRect(0, 0, width, height);
Blink Reformat4c46d092018-04-07 15:32:37934 context.translate(0, -top);
935 const defaultFont = '11px ' + Host.fontFamily();
936 context.font = defaultFont;
937
938 const entryTotalTimes = timelineData.entryTotalTimes;
939 const entryStartTimes = timelineData.entryStartTimes;
940 const entryLevels = timelineData.entryLevels;
941 const timeToPixel = this._chartViewport.timeToPixel();
942
943 const titleIndices = [];
944 const markerIndices = [];
945 const textPadding = this._textPadding;
946 const minTextWidth = 2 * textPadding + UI.measureTextWidth(context, '\u2026');
Alexei Filippov57ccafb2018-08-14 20:59:05947 const minTextWidthDuration = this._chartViewport.pixelToTimeOffset(minTextWidth);
Blink Reformat4c46d092018-04-07 15:32:37948 const minVisibleBarLevel = Math.max(this._visibleLevelOffsets.upperBound(top) - 1, 0);
Alexei Filippov6c622e92018-11-10 02:13:59949 this._markerPositions.clear();
Blink Reformat4c46d092018-04-07 15:32:37950
Blink Reformat4c46d092018-04-07 15:32:37951 /** @type {!Map<string, !Array<number>>} */
952 const colorBuckets = new Map();
953 for (let level = minVisibleBarLevel; level < this._dataProvider.maxStackDepth(); ++level) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34954 if (this._levelToOffset(level) > top + height) {
Blink Reformat4c46d092018-04-07 15:32:37955 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:34956 }
957 if (!this._visibleLevels[level]) {
Blink Reformat4c46d092018-04-07 15:32:37958 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34959 }
Blink Reformat4c46d092018-04-07 15:32:37960
961 // Entries are ordered by start time within a level, so find the last visible entry.
962 const levelIndexes = this._timelineLevels[level];
963 const rightIndexOnLevel =
964 levelIndexes.lowerBound(
965 this._chartViewport.windowRightTime(), (time, entryIndex) => time - entryStartTimes[entryIndex]) -
966 1;
967 let lastDrawOffset = Infinity;
968 for (let entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
969 const entryIndex = levelIndexes[entryIndexOnLevel];
Alexei Filippov72d792d2018-11-06 07:15:04970 const duration = entryTotalTimes[entryIndex];
971 if (isNaN(duration)) {
Alexei Filippov57ccafb2018-08-14 20:59:05972 markerIndices.push(entryIndex);
Alexei Filippov72d792d2018-11-06 07:15:04973 continue;
974 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34975 if (duration >= minTextWidthDuration || this._forceDecorationCache[entryIndex]) {
Alexei Filippov57ccafb2018-08-14 20:59:05976 titleIndices.push(entryIndex);
Tim van der Lippe1d6e57a2019-09-30 11:55:34977 }
Alexei Filippov57ccafb2018-08-14 20:59:05978
Blink Reformat4c46d092018-04-07 15:32:37979 const entryStartTime = entryStartTimes[entryIndex];
Alexei Filippov57ccafb2018-08-14 20:59:05980 const entryOffsetRight = entryStartTime + duration;
Tim van der Lippe1d6e57a2019-09-30 11:55:34981 if (entryOffsetRight <= this._chartViewport.windowLeftTime()) {
Blink Reformat4c46d092018-04-07 15:32:37982 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:34983 }
984 if (this._useWebGL) {
Alexei Filippov57ccafb2018-08-14 20:59:05985 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34986 }
Blink Reformat4c46d092018-04-07 15:32:37987
988 const barX = this._timeToPositionClipped(entryStartTime);
989 // Check if the entry entirely fits into an already drawn pixel, we can just skip drawing it.
Tim van der Lippe1d6e57a2019-09-30 11:55:34990 if (barX >= lastDrawOffset) {
Blink Reformat4c46d092018-04-07 15:32:37991 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34992 }
Blink Reformat4c46d092018-04-07 15:32:37993 lastDrawOffset = barX;
994
Alexei Filippove5197622018-08-18 03:04:33995 const color = this._entryColorsCache[entryIndex];
Blink Reformat4c46d092018-04-07 15:32:37996 let bucket = colorBuckets.get(color);
997 if (!bucket) {
998 bucket = [];
999 colorBuckets.set(color, bucket);
1000 }
1001 bucket.push(entryIndex);
1002 }
1003 }
1004
Alexei Filippov57ccafb2018-08-14 20:59:051005 if (this._useWebGL) {
1006 this._drawGL();
1007 } else {
1008 context.save();
Alexei Filippov5f6b11d2018-08-18 03:30:281009 this._forEachGroupInViewport((offset, index, group, isFirst, groupHeight) => {
Anubha Mathur72dd5822019-06-13 23:05:191010 if (this._isGroupFocused(index)) {
Alexei Filippov57ccafb2018-08-14 20:59:051011 context.fillStyle = this._selectedGroupBackroundColor;
1012 context.fillRect(0, offset, width, groupHeight - group.style.padding);
Blink Reformat4c46d092018-04-07 15:32:371013 }
Alexei Filippov57ccafb2018-08-14 20:59:051014 });
1015 context.restore();
1016
Alexei Filippov72d792d2018-11-06 07:15:041017 for (const [color, indexes] of colorBuckets) {
Alexei Filippov57ccafb2018-08-14 20:59:051018 context.beginPath();
1019 for (let i = 0; i < indexes.length; ++i) {
1020 const entryIndex = indexes[i];
Alexei Filippov72d792d2018-11-06 07:15:041021 const duration = entryTotalTimes[entryIndex];
Tim van der Lippe1d6e57a2019-09-30 11:55:341022 if (isNaN(duration)) {
Alexei Filippov72d792d2018-11-06 07:15:041023 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341024 }
Alexei Filippov57ccafb2018-08-14 20:59:051025 const entryStartTime = entryStartTimes[entryIndex];
1026 const barX = this._timeToPositionClipped(entryStartTime);
Alexei Filippov57ccafb2018-08-14 20:59:051027 const barLevel = entryLevels[entryIndex];
1028 const barHeight = this._levelHeight(barLevel);
1029 const barY = this._levelToOffset(barLevel);
Alexei Filippov57ccafb2018-08-14 20:59:051030 const barRight = this._timeToPositionClipped(entryStartTime + duration);
1031 const barWidth = Math.max(barRight - barX, 1);
Alexei Filippov72d792d2018-11-06 07:15:041032 context.rect(barX, barY, barWidth - 0.4, barHeight - 1);
Alexei Filippov57ccafb2018-08-14 20:59:051033 }
Alexei Filippov57ccafb2018-08-14 20:59:051034 context.fillStyle = color;
1035 context.fill();
Blink Reformat4c46d092018-04-07 15:32:371036 }
Blink Reformat4c46d092018-04-07 15:32:371037 }
1038
Alexei Filippov72d792d2018-11-06 07:15:041039 context.textBaseline = 'alphabetic';
Blink Reformat4c46d092018-04-07 15:32:371040 context.beginPath();
Alexei Filippov6c622e92018-11-10 02:13:591041 let lastMarkerLevel = -1;
1042 let lastMarkerX = -Infinity;
1043 // Markers are sorted top to bottom, right to left.
Alexei Filippov72d792d2018-11-06 07:15:041044 for (let m = markerIndices.length - 1; m >= 0; --m) {
Blink Reformat4c46d092018-04-07 15:32:371045 const entryIndex = markerIndices[m];
Alexei Filippov72d792d2018-11-06 07:15:041046 const title = this._dataProvider.entryTitle(entryIndex);
Tim van der Lippe1d6e57a2019-09-30 11:55:341047 if (!title) {
Alexei Filippov72d792d2018-11-06 07:15:041048 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341049 }
Alexei Filippov6c622e92018-11-10 02:13:591050 const entryStartTime = entryStartTimes[entryIndex];
1051 const level = entryLevels[entryIndex];
Tim van der Lippe1d6e57a2019-09-30 11:55:341052 if (lastMarkerLevel !== level) {
Alexei Filippov6c622e92018-11-10 02:13:591053 lastMarkerX = -Infinity;
Tim van der Lippe1d6e57a2019-09-30 11:55:341054 }
Alexei Filippov6c622e92018-11-10 02:13:591055 const x = Math.max(this._chartViewport.timeToPosition(entryStartTime), lastMarkerX);
1056 const y = this._levelToOffset(level);
1057 const h = this._levelHeight(level);
1058 const padding = 4;
Alexei Filippov72d792d2018-11-06 07:15:041059 const width = Math.ceil(UI.measureTextWidth(context, title)) + 2 * padding;
Alexei Filippov214df612018-11-13 19:14:001060 lastMarkerX = x + width + 1;
Alexei Filippov6c622e92018-11-10 02:13:591061 lastMarkerLevel = level;
1062 this._markerPositions.set(entryIndex, {x, width});
Alexei Filippov72d792d2018-11-06 07:15:041063 context.fillStyle = this._dataProvider.entryColor(entryIndex);
1064 context.fillRect(x, y, width, h - 1);
1065 context.fillStyle = 'white';
Alexei Filippov72d792d2018-11-06 07:15:041066 context.fillText(title, x + padding, y + h - this._textBaseline);
Blink Reformat4c46d092018-04-07 15:32:371067 }
1068 context.strokeStyle = 'rgba(0, 0, 0, 0.2)';
1069 context.stroke();
1070
Blink Reformat4c46d092018-04-07 15:32:371071 for (let i = 0; i < titleIndices.length; ++i) {
1072 const entryIndex = titleIndices[i];
1073 const entryStartTime = entryStartTimes[entryIndex];
1074 const barX = this._timeToPositionClipped(entryStartTime);
1075 const barRight = Math.min(this._timeToPositionClipped(entryStartTime + entryTotalTimes[entryIndex]), width) + 1;
1076 const barWidth = barRight - barX;
1077 const barLevel = entryLevels[entryIndex];
1078 const barY = this._levelToOffset(barLevel);
1079 let text = this._dataProvider.entryTitle(entryIndex);
1080 if (text && text.length) {
1081 context.font = this._dataProvider.entryFont(entryIndex) || defaultFont;
1082 text = UI.trimTextMiddle(context, text, barWidth - 2 * textPadding);
1083 }
1084 const unclippedBarX = this._chartViewport.timeToPosition(entryStartTime);
1085 const barHeight = this._levelHeight(barLevel);
1086 if (this._dataProvider.decorateEntry(
Tim van der Lippe1d6e57a2019-09-30 11:55:341087 entryIndex, context, text, barX, barY, barWidth, barHeight, unclippedBarX, timeToPixel)) {
Blink Reformat4c46d092018-04-07 15:32:371088 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341089 }
1090 if (!text || !text.length) {
Blink Reformat4c46d092018-04-07 15:32:371091 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341092 }
Blink Reformat4c46d092018-04-07 15:32:371093 context.fillStyle = this._dataProvider.textColor(entryIndex);
1094 context.fillText(text, barX + textPadding, barY + barHeight - this._textBaseline);
1095 }
1096
1097 context.restore();
1098
1099 this._drawGroupHeaders(width, height);
1100 this._drawFlowEvents(context, width, height);
1101 this._drawMarkers();
1102 const dividersData = PerfUI.TimelineGrid.calculateGridOffsets(this);
1103 PerfUI.TimelineGrid.drawCanvasGrid(context, dividersData);
1104 if (this._rulerEnabled) {
1105 PerfUI.TimelineGrid.drawCanvasHeaders(
1106 context, dividersData, time => this.formatValue(time, dividersData.precision), 3,
1107 PerfUI.FlameChart.HeaderHeight);
1108 }
1109
1110 this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
1111 this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
1112 this._updateMarkerHighlight();
1113 }
1114
Alexei Filippov57ccafb2018-08-14 20:59:051115 _initWebGL() {
1116 const gl = /** @type {?WebGLRenderingContext} */ (this._canvasGL.getContext('webgl'));
1117 if (!gl) {
1118 console.error('Failed to obtain WebGL context.');
1119 this._useWebGL = false; // Fallback to use canvas.
1120 return;
1121 }
1122
1123 const vertexShaderSource = `
1124 attribute vec2 aVertexPosition;
Alexei Filippovaf3ffed2018-08-18 01:56:091125 attribute float aVertexColor;
Alexei Filippov57ccafb2018-08-14 20:59:051126
1127 uniform vec2 uScalingFactor;
1128 uniform vec2 uShiftVector;
1129
Alexei Filippovaf3ffed2018-08-18 01:56:091130 varying mediump vec2 vPalettePosition;
Alexei Filippov57ccafb2018-08-14 20:59:051131
1132 void main() {
1133 vec2 shiftedPosition = aVertexPosition - uShiftVector;
1134 gl_Position = vec4(shiftedPosition * uScalingFactor + vec2(-1.0, 1.0), 0.0, 1.0);
Alexei Filippovaf3ffed2018-08-18 01:56:091135 vPalettePosition = vec2(aVertexColor, 0.5);
Alexei Filippov57ccafb2018-08-14 20:59:051136 }`;
1137
1138 const fragmentShaderSource = `
Alexei Filippovaf3ffed2018-08-18 01:56:091139 varying mediump vec2 vPalettePosition;
1140 uniform sampler2D uSampler;
Alexei Filippov57ccafb2018-08-14 20:59:051141
1142 void main() {
Alexei Filippovaf3ffed2018-08-18 01:56:091143 gl_FragColor = texture2D(uSampler, vPalettePosition);
Alexei Filippov57ccafb2018-08-14 20:59:051144 }`;
1145
1146 /**
1147 * @param {!WebGLRenderingContext} gl
1148 * @param {number} type
1149 * @param {string} source
1150 * @return {?WebGLShader}
1151 */
1152 function loadShader(gl, type, source) {
1153 const shader = gl.createShader(type);
1154 gl.shaderSource(shader, source);
1155 gl.compileShader(shader);
Tim van der Lippe1d6e57a2019-09-30 11:55:341156 if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
Alexei Filippov57ccafb2018-08-14 20:59:051157 return shader;
Tim van der Lippe1d6e57a2019-09-30 11:55:341158 }
Alexei Filippov57ccafb2018-08-14 20:59:051159 console.error('Shader compile error: ' + gl.getShaderInfoLog(shader));
1160 gl.deleteShader(shader);
1161 return null;
1162 }
1163
1164 const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
1165 const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
1166
1167 const shaderProgram = gl.createProgram();
1168 gl.attachShader(shaderProgram, vertexShader);
1169 gl.attachShader(shaderProgram, fragmentShader);
1170 gl.linkProgram(shaderProgram);
1171
1172 if (gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
1173 this._shaderProgram = shaderProgram;
1174 gl.useProgram(shaderProgram);
1175 } else {
1176 console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
1177 this._shaderProgram = null;
1178 }
Alexei Filippovaf3ffed2018-08-18 01:56:091179
1180 this._vertexBuffer = gl.createBuffer();
1181 this._colorBuffer = gl.createBuffer();
1182
1183 this._uScalingFactor = gl.getUniformLocation(shaderProgram, 'uScalingFactor');
1184 this._uShiftVector = gl.getUniformLocation(shaderProgram, 'uShiftVector');
1185 const uSampler = gl.getUniformLocation(shaderProgram, 'uSampler');
1186 gl.uniform1i(uSampler, 0);
1187 this._aVertexPosition = gl.getAttribLocation(this._shaderProgram, 'aVertexPosition');
1188 this._aVertexColor = gl.getAttribLocation(this._shaderProgram, 'aVertexColor');
1189 gl.enableVertexAttribArray(this._aVertexPosition);
1190 gl.enableVertexAttribArray(this._aVertexColor);
Alexei Filippov57ccafb2018-08-14 20:59:051191 }
1192
1193 _setupGLGeometry() {
1194 const gl = /** @type {?WebGLRenderingContext} */ (this._canvasGL.getContext('webgl'));
Tim van der Lippe1d6e57a2019-09-30 11:55:341195 if (!gl) {
Alexei Filippov57ccafb2018-08-14 20:59:051196 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341197 }
Alexei Filippov57ccafb2018-08-14 20:59:051198
1199 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:341200 if (!timelineData) {
Alexei Filippov57ccafb2018-08-14 20:59:051201 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341202 }
Alexei Filippov57ccafb2018-08-14 20:59:051203
1204 const entryTotalTimes = timelineData.entryTotalTimes;
1205 const entryStartTimes = timelineData.entryStartTimes;
1206 const entryLevels = timelineData.entryLevels;
1207
Alexei Filippovaf3ffed2018-08-18 01:56:091208 const verticesPerBar = 6;
1209 const vertexArray = new Float32Array(entryTotalTimes.length * verticesPerBar * 2);
1210 let colorArray = new Uint8Array(entryTotalTimes.length * verticesPerBar);
Alexei Filippov57ccafb2018-08-14 20:59:051211 let vertex = 0;
Alexei Filippovaf3ffed2018-08-18 01:56:091212 /** @type {!Map<string, number>} */
Alexei Filippovc34372c2018-08-16 20:37:391213 const parsedColorCache = new Map();
Alexei Filippovaf3ffed2018-08-18 01:56:091214 /** @type {!Array<number>} */
1215 const colors = [];
1216
Alexei Filippov5f6b11d2018-08-18 03:30:281217 const collapsedOverviewLevels = new Array(this._visibleLevels.length);
1218 const groups = this._rawTimelineData.groups || [];
1219 this._forEachGroup((offset, index, group) => {
Tim van der Lippe1d6e57a2019-09-30 11:55:341220 if (group.style.useFirstLineForOverview || !this._isGroupCollapsible(index) || group.expanded) {
Alexei Filippov5f6b11d2018-08-18 03:30:281221 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341222 }
Alexei Filippov5f6b11d2018-08-18 03:30:281223 let nextGroup = index + 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:341224 while (nextGroup < groups.length && groups[nextGroup].style.nestingLevel > group.style.nestingLevel) {
Alexei Filippov5f6b11d2018-08-18 03:30:281225 ++nextGroup;
Tim van der Lippe1d6e57a2019-09-30 11:55:341226 }
Alexei Filippov5f6b11d2018-08-18 03:30:281227 const endLevel = nextGroup < groups.length ? groups[nextGroup].startLevel : this._dataProvider.maxStackDepth();
Tim van der Lippe1d6e57a2019-09-30 11:55:341228 for (let i = group.startLevel; i < endLevel; ++i) {
Alexei Filippov5f6b11d2018-08-18 03:30:281229 collapsedOverviewLevels[i] = offset;
Tim van der Lippe1d6e57a2019-09-30 11:55:341230 }
Alexei Filippov5f6b11d2018-08-18 03:30:281231 });
1232
Alexei Filippov57ccafb2018-08-14 20:59:051233 for (let i = 0; i < entryTotalTimes.length; ++i) {
1234 const level = entryLevels[i];
Alexei Filippov5f6b11d2018-08-18 03:30:281235 const collapsedGroupOffset = collapsedOverviewLevels[level];
Tim van der Lippe1d6e57a2019-09-30 11:55:341236 if (!this._visibleLevels[level] && !collapsedGroupOffset) {
Alexei Filippov57ccafb2018-08-14 20:59:051237 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341238 }
Alexei Filippove5197622018-08-18 03:04:331239 const color = this._entryColorsCache[i];
Tim van der Lippe1d6e57a2019-09-30 11:55:341240 if (!color) {
Alexei Filippov57ccafb2018-08-14 20:59:051241 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341242 }
Alexei Filippovaf3ffed2018-08-18 01:56:091243 let colorIndex = parsedColorCache.get(color);
1244 if (colorIndex === undefined) {
1245 const rgba = Common.Color.parse(color).canonicalRGBA();
Alexei Filippovc34372c2018-08-16 20:37:391246 rgba[3] = Math.round(rgba[3] * 255);
Alexei Filippovaf3ffed2018-08-18 01:56:091247 colorIndex = colors.length / 4;
1248 colors.push(...rgba);
Tim van der Lippe1d6e57a2019-09-30 11:55:341249 if (colorIndex === 256) {
Alexei Filippovaf3ffed2018-08-18 01:56:091250 colorArray = new Uint16Array(colorArray);
Tim van der Lippe1d6e57a2019-09-30 11:55:341251 }
Alexei Filippovaf3ffed2018-08-18 01:56:091252 parsedColorCache.set(color, colorIndex);
Alexei Filippovc34372c2018-08-16 20:37:391253 }
Tim van der Lippe1d6e57a2019-09-30 11:55:341254 for (let j = 0; j < verticesPerBar; ++j) {
Alexei Filippovaf3ffed2018-08-18 01:56:091255 colorArray[vertex + j] = colorIndex;
Tim van der Lippe1d6e57a2019-09-30 11:55:341256 }
Alexei Filippov57ccafb2018-08-14 20:59:051257
1258 const vpos = vertex * 2;
1259 const x0 = entryStartTimes[i] - this._minimumBoundary;
1260 const x1 = x0 + entryTotalTimes[i];
Alexei Filippov5f6b11d2018-08-18 03:30:281261 const y0 = collapsedGroupOffset || this._levelToOffset(level);
Alexei Filippov57ccafb2018-08-14 20:59:051262 const y1 = y0 + this._levelHeight(level) - 1;
1263 vertexArray[vpos + 0] = x0;
1264 vertexArray[vpos + 1] = y0;
1265 vertexArray[vpos + 2] = x1;
1266 vertexArray[vpos + 3] = y0;
1267 vertexArray[vpos + 4] = x0;
1268 vertexArray[vpos + 5] = y1;
1269 vertexArray[vpos + 6] = x0;
1270 vertexArray[vpos + 7] = y1;
1271 vertexArray[vpos + 8] = x1;
1272 vertexArray[vpos + 9] = y0;
1273 vertexArray[vpos + 10] = x1;
1274 vertexArray[vpos + 11] = y1;
1275
Alexei Filippovaf3ffed2018-08-18 01:56:091276 vertex += verticesPerBar;
Alexei Filippov57ccafb2018-08-14 20:59:051277 }
Alexei Filippov57ccafb2018-08-14 20:59:051278 this._vertexCount = vertex;
1279
Alexei Filippovaf3ffed2018-08-18 01:56:091280 const paletteTexture = gl.createTexture();
1281 gl.bindTexture(gl.TEXTURE_2D, paletteTexture);
1282 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
1283 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
1284 gl.activeTexture(gl.TEXTURE0);
1285
1286 const numColors = colors.length / 4;
1287 const useShortForColors = numColors >= 256;
1288 const width = !useShortForColors ? 256 : Math.min(1 << 16, gl.getParameter(gl.MAX_TEXTURE_SIZE));
1289 console.assert(numColors <= width, 'Too many colors');
1290 const height = 1;
1291 const colorIndexType = useShortForColors ? gl.UNSIGNED_SHORT : gl.UNSIGNED_BYTE;
1292 if (useShortForColors) {
1293 const factor = (1 << 16) / width;
Tim van der Lippe1d6e57a2019-09-30 11:55:341294 for (let i = 0; i < vertex; ++i) {
Alexei Filippovaf3ffed2018-08-18 01:56:091295 colorArray[i] *= factor;
Tim van der Lippe1d6e57a2019-09-30 11:55:341296 }
Alexei Filippovaf3ffed2018-08-18 01:56:091297 }
1298
1299 const pixels = new Uint8Array(width * 4);
1300 pixels.set(colors);
1301 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
1302
1303 gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
Alexei Filippov57ccafb2018-08-14 20:59:051304 gl.bufferData(gl.ARRAY_BUFFER, vertexArray, gl.STATIC_DRAW);
Alexei Filippovaf3ffed2018-08-18 01:56:091305 gl.vertexAttribPointer(this._aVertexPosition, /* vertexComponents */ 2, gl.FLOAT, false, 0, 0);
Alexei Filippov57ccafb2018-08-14 20:59:051306
Alexei Filippovaf3ffed2018-08-18 01:56:091307 gl.bindBuffer(gl.ARRAY_BUFFER, this._colorBuffer);
Alexei Filippov57ccafb2018-08-14 20:59:051308 gl.bufferData(gl.ARRAY_BUFFER, colorArray, gl.STATIC_DRAW);
Alexei Filippovaf3ffed2018-08-18 01:56:091309 gl.vertexAttribPointer(this._aVertexColor, /* colorComponents */ 1, colorIndexType, true, 0, 0);
Alexei Filippov57ccafb2018-08-14 20:59:051310 }
1311
1312 _drawGL() {
1313 const gl = /** @type {?WebGLRenderingContext} */ (this._canvasGL.getContext('webgl'));
Tim van der Lippe1d6e57a2019-09-30 11:55:341314 if (!gl) {
Alexei Filippov57ccafb2018-08-14 20:59:051315 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341316 }
Alexei Filippov57ccafb2018-08-14 20:59:051317 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:341318 if (!timelineData) {
Alexei Filippov57ccafb2018-08-14 20:59:051319 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341320 }
Alexei Filippov57ccafb2018-08-14 20:59:051321
Alexei Filippov57ccafb2018-08-14 20:59:051322 if (!this._prevTimelineData || timelineData.entryTotalTimes !== this._prevTimelineData.entryTotalTimes) {
1323 this._prevTimelineData = timelineData;
1324 this._setupGLGeometry();
1325 }
1326
Alexei Filippovc34372c2018-08-16 20:37:391327 gl.viewport(0, 0, this._canvasGL.width, this._canvasGL.height);
Alexei Filippov57ccafb2018-08-14 20:59:051328
Tim van der Lippe1d6e57a2019-09-30 11:55:341329 if (!this._vertexCount) {
Alexei Filippov57ccafb2018-08-14 20:59:051330 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341331 }
Alexei Filippov57ccafb2018-08-14 20:59:051332
Alexei Filippovc34372c2018-08-16 20:37:391333 const viewportScale = [2.0 / this.boundarySpan(), -2.0 * window.devicePixelRatio / this._canvasGL.height];
1334 const viewportShift = [this.minimumBoundary() - this.zeroTime(), this._chartViewport.scrollOffset()];
1335 gl.uniform2fv(this._uScalingFactor, viewportScale);
1336 gl.uniform2fv(this._uShiftVector, viewportShift);
Alexei Filippov57ccafb2018-08-14 20:59:051337
1338 gl.drawArrays(gl.TRIANGLES, 0, this._vertexCount);
1339 }
1340
Blink Reformat4c46d092018-04-07 15:32:371341 /**
1342 * @param {number} width
1343 * @param {number} height
1344 */
1345 _drawGroupHeaders(width, height) {
1346 const context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
1347 const top = this._chartViewport.scrollOffset();
1348 const ratio = window.devicePixelRatio;
1349 const groups = this._rawTimelineData.groups || [];
Tim van der Lippe1d6e57a2019-09-30 11:55:341350 if (!groups.length) {
Blink Reformat4c46d092018-04-07 15:32:371351 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341352 }
Blink Reformat4c46d092018-04-07 15:32:371353
1354 const groupOffsets = this._groupOffsets;
1355 const lastGroupOffset = Array.prototype.peekLast.call(groupOffsets);
1356 const colorUsage = UI.ThemeSupport.ColorUsage;
1357
1358 context.save();
1359 context.scale(ratio, ratio);
1360 context.translate(0, -top);
1361 const defaultFont = '11px ' + Host.fontFamily();
1362 context.font = defaultFont;
1363
1364 context.fillStyle = UI.themeSupport.patchColorText('#fff', colorUsage.Background);
Alexei Filippov5f6b11d2018-08-18 03:30:281365 this._forEachGroupInViewport((offset, index, group) => {
Blink Reformat4c46d092018-04-07 15:32:371366 const paddingHeight = group.style.padding;
Tim van der Lippe1d6e57a2019-09-30 11:55:341367 if (paddingHeight < 5) {
Blink Reformat4c46d092018-04-07 15:32:371368 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341369 }
Blink Reformat4c46d092018-04-07 15:32:371370 context.fillRect(0, offset - paddingHeight + 2, width, paddingHeight - 4);
1371 });
Tim van der Lippe1d6e57a2019-09-30 11:55:341372 if (groups.length && lastGroupOffset < top + height) {
Blink Reformat4c46d092018-04-07 15:32:371373 context.fillRect(0, lastGroupOffset + 2, width, top + height - lastGroupOffset);
Tim van der Lippe1d6e57a2019-09-30 11:55:341374 }
Blink Reformat4c46d092018-04-07 15:32:371375
1376 context.strokeStyle = UI.themeSupport.patchColorText('#eee', colorUsage.Background);
1377 context.beginPath();
Alexei Filippov5f6b11d2018-08-18 03:30:281378 this._forEachGroupInViewport((offset, index, group, isFirst) => {
Tim van der Lippe1d6e57a2019-09-30 11:55:341379 if (isFirst || group.style.padding < 4) {
Blink Reformat4c46d092018-04-07 15:32:371380 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341381 }
Blink Reformat4c46d092018-04-07 15:32:371382 hLine(offset - 2.5);
1383 });
1384 hLine(lastGroupOffset + 1.5);
1385 context.stroke();
1386
Alexei Filippov5f6b11d2018-08-18 03:30:281387 this._forEachGroupInViewport((offset, index, group) => {
Tim van der Lippe1d6e57a2019-09-30 11:55:341388 if (group.style.useFirstLineForOverview) {
Blink Reformat4c46d092018-04-07 15:32:371389 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341390 }
Blink Reformat4c46d092018-04-07 15:32:371391 if (!this._isGroupCollapsible(index) || group.expanded) {
Anubha Mathur72dd5822019-06-13 23:05:191392 if (!group.style.shareHeaderLine && this._isGroupFocused(index)) {
Blink Reformat4c46d092018-04-07 15:32:371393 context.fillStyle = group.style.backgroundColor;
1394 context.fillRect(0, offset, width, group.style.height);
1395 }
1396 return;
1397 }
Tim van der Lippe1d6e57a2019-09-30 11:55:341398 if (this._useWebGL) {
Alexei Filippov5f6b11d2018-08-18 03:30:281399 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341400 }
Blink Reformat4c46d092018-04-07 15:32:371401 let nextGroup = index + 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:341402 while (nextGroup < groups.length && groups[nextGroup].style.nestingLevel > group.style.nestingLevel) {
Blink Reformat4c46d092018-04-07 15:32:371403 nextGroup++;
Tim van der Lippe1d6e57a2019-09-30 11:55:341404 }
Blink Reformat4c46d092018-04-07 15:32:371405 const endLevel = nextGroup < groups.length ? groups[nextGroup].startLevel : this._dataProvider.maxStackDepth();
Alexei Filippov23428d42018-04-28 01:09:071406 this._drawCollapsedOverviewForGroup(group, offset, endLevel);
Blink Reformat4c46d092018-04-07 15:32:371407 });
1408
1409 context.save();
Alexei Filippov5f6b11d2018-08-18 03:30:281410 this._forEachGroupInViewport((offset, index, group) => {
Blink Reformat4c46d092018-04-07 15:32:371411 context.font = group.style.font;
1412 if (this._isGroupCollapsible(index) && !group.expanded || group.style.shareHeaderLine) {
1413 const width = this._labelWidthForGroup(context, group) + 2;
Tim van der Lippe1d6e57a2019-09-30 11:55:341414 if (this._isGroupFocused(index)) {
Blink Reformat4c46d092018-04-07 15:32:371415 context.fillStyle = this._selectedGroupBackroundColor;
Tim van der Lippe1d6e57a2019-09-30 11:55:341416 } else {
Blink Reformat4c46d092018-04-07 15:32:371417 context.fillStyle = Common.Color.parse(group.style.backgroundColor).setAlpha(0.8).asString(null);
Tim van der Lippe1d6e57a2019-09-30 11:55:341418 }
Blink Reformat4c46d092018-04-07 15:32:371419
1420 context.fillRect(
1421 this._headerLeftPadding - this._headerLabelXPadding, offset + this._headerLabelYPadding, width,
1422 group.style.height - 2 * this._headerLabelYPadding);
1423 }
1424 context.fillStyle = group.style.color;
1425 context.fillText(
1426 group.name, Math.floor(this._expansionArrowIndent * (group.style.nestingLevel + 1) + this._arrowSide),
1427 offset + group.style.height - this._textBaseline);
1428 });
1429 context.restore();
1430
1431 context.fillStyle = UI.themeSupport.patchColorText('#6e6e6e', colorUsage.Foreground);
1432 context.beginPath();
Alexei Filippov5f6b11d2018-08-18 03:30:281433 this._forEachGroupInViewport((offset, index, group) => {
Blink Reformat4c46d092018-04-07 15:32:371434 if (this._isGroupCollapsible(index)) {
1435 drawExpansionArrow.call(
1436 this, this._expansionArrowIndent * (group.style.nestingLevel + 1),
1437 offset + group.style.height - this._textBaseline - this._arrowSide / 2, !!group.expanded);
1438 }
1439 });
1440 context.fill();
1441
1442 context.strokeStyle = UI.themeSupport.patchColorText('#ddd', colorUsage.Background);
1443 context.beginPath();
1444 context.stroke();
1445
Alexei Filippov5f6b11d2018-08-18 03:30:281446 this._forEachGroupInViewport((offset, index, group, isFirst, groupHeight) => {
Anubha Mathur72dd5822019-06-13 23:05:191447 if (this._isGroupFocused(index)) {
Alexei Filippov23428d42018-04-28 01:09:071448 const lineWidth = 2;
1449 const bracketLength = 10;
Blink Reformat4c46d092018-04-07 15:32:371450 context.fillStyle = this._selectedGroupBorderColor;
Alexei Filippov23428d42018-04-28 01:09:071451 context.fillRect(0, offset - lineWidth, lineWidth, groupHeight - group.style.padding + 2 * lineWidth);
1452 context.fillRect(0, offset - lineWidth, bracketLength, lineWidth);
1453 context.fillRect(0, offset + groupHeight - group.style.padding, bracketLength, lineWidth);
Blink Reformat4c46d092018-04-07 15:32:371454 }
1455 });
1456
1457 context.restore();
1458
1459 /**
1460 * @param {number} y
1461 */
1462 function hLine(y) {
1463 context.moveTo(0, y);
1464 context.lineTo(width, y);
1465 }
1466
1467 /**
1468 * @param {number} x
1469 * @param {number} y
1470 * @param {boolean} expanded
1471 * @this {PerfUI.FlameChart}
1472 */
1473 function drawExpansionArrow(x, y, expanded) {
1474 const arrowHeight = this._arrowSide * Math.sqrt(3) / 2;
1475 const arrowCenterOffset = Math.round(arrowHeight / 2);
1476 context.save();
1477 context.translate(x, y);
1478 context.rotate(expanded ? Math.PI / 2 : 0);
1479 context.moveTo(-arrowCenterOffset, -this._arrowSide / 2);
1480 context.lineTo(-arrowCenterOffset, this._arrowSide / 2);
1481 context.lineTo(arrowHeight - arrowCenterOffset, 0);
1482 context.restore();
1483 }
1484 }
1485
1486 /**
1487 * @param {function(number, number, !PerfUI.FlameChart.Group, boolean, number)} callback
1488 */
1489 _forEachGroup(callback) {
Blink Reformat4c46d092018-04-07 15:32:371490 const groups = this._rawTimelineData.groups || [];
Tim van der Lippe1d6e57a2019-09-30 11:55:341491 if (!groups.length) {
Blink Reformat4c46d092018-04-07 15:32:371492 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341493 }
Blink Reformat4c46d092018-04-07 15:32:371494 const groupOffsets = this._groupOffsets;
1495 /** @type !Array<{nestingLevel: number, visible: boolean}> */
1496 const groupStack = [{nestingLevel: -1, visible: true}];
1497 for (let i = 0; i < groups.length; ++i) {
1498 const groupTop = groupOffsets[i];
1499 const group = groups[i];
Blink Reformat4c46d092018-04-07 15:32:371500 let firstGroup = true;
1501 while (groupStack.peekLast().nestingLevel >= group.style.nestingLevel) {
1502 groupStack.pop();
1503 firstGroup = false;
1504 }
1505 const parentGroupVisible = groupStack.peekLast().visible;
1506 const thisGroupVisible = parentGroupVisible && (!this._isGroupCollapsible(i) || group.expanded);
1507 groupStack.push({nestingLevel: group.style.nestingLevel, visible: thisGroupVisible});
Alexei Filippov23428d42018-04-28 01:09:071508 const nextOffset = i === groups.length - 1 ? groupOffsets[i + 1] + group.style.padding : groupOffsets[i + 1];
Tim van der Lippe1d6e57a2019-09-30 11:55:341509 if (!parentGroupVisible) {
Blink Reformat4c46d092018-04-07 15:32:371510 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341511 }
Blink Reformat4c46d092018-04-07 15:32:371512 callback(groupTop, i, group, firstGroup, nextOffset - groupTop);
1513 }
1514 }
1515
1516 /**
Alexei Filippov5f6b11d2018-08-18 03:30:281517 * @param {function(number, number, !PerfUI.FlameChart.Group, boolean, number)} callback
1518 */
1519 _forEachGroupInViewport(callback) {
1520 const top = this._chartViewport.scrollOffset();
1521 this._forEachGroup((groupTop, index, group, firstGroup, height) => {
Tim van der Lippe1d6e57a2019-09-30 11:55:341522 if (groupTop - group.style.padding > top + this._offsetHeight) {
Alexei Filippov5f6b11d2018-08-18 03:30:281523 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341524 }
1525 if (groupTop + height < top) {
Alexei Filippov5f6b11d2018-08-18 03:30:281526 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341527 }
Alexei Filippov5f6b11d2018-08-18 03:30:281528 callback(groupTop, index, group, firstGroup, height);
1529 });
1530 }
1531
1532 /**
Blink Reformat4c46d092018-04-07 15:32:371533 * @param {!CanvasRenderingContext2D} context
1534 * @param {!PerfUI.FlameChart.Group} group
1535 * @return {number}
1536 */
1537 _labelWidthForGroup(context, group) {
1538 return UI.measureTextWidth(context, group.name) + this._expansionArrowIndent * (group.style.nestingLevel + 1) +
1539 2 * this._headerLabelXPadding;
1540 }
1541
1542 /**
1543 * @param {!PerfUI.FlameChart.Group} group
1544 * @param {number} y
1545 * @param {number} endLevel
1546 */
1547 _drawCollapsedOverviewForGroup(group, y, endLevel) {
1548 const range = new Common.SegmentedRange(mergeCallback);
1549 const timeWindowLeft = this._chartViewport.windowLeftTime();
1550 const timeWindowRight = this._chartViewport.windowRightTime();
1551 const context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
1552 const barHeight = group.style.height;
1553 const entryStartTimes = this._rawTimelineData.entryStartTimes;
1554 const entryTotalTimes = this._rawTimelineData.entryTotalTimes;
1555 const timeToPixel = this._chartViewport.timeToPixel();
1556
1557 for (let level = group.startLevel; level < endLevel; ++level) {
1558 const levelIndexes = this._timelineLevels[level];
1559 const rightIndexOnLevel =
1560 levelIndexes.lowerBound(timeWindowRight, (time, entryIndex) => time - entryStartTimes[entryIndex]) - 1;
1561 let lastDrawOffset = Infinity;
1562
1563 for (let entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
1564 const entryIndex = levelIndexes[entryIndexOnLevel];
1565 const entryStartTime = entryStartTimes[entryIndex];
1566 const barX = this._timeToPositionClipped(entryStartTime);
1567 const entryEndTime = entryStartTime + entryTotalTimes[entryIndex];
Tim van der Lippe1d6e57a2019-09-30 11:55:341568 if (isNaN(entryEndTime) || barX >= lastDrawOffset) {
Blink Reformat4c46d092018-04-07 15:32:371569 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341570 }
1571 if (entryEndTime <= timeWindowLeft) {
Blink Reformat4c46d092018-04-07 15:32:371572 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:341573 }
Blink Reformat4c46d092018-04-07 15:32:371574 lastDrawOffset = barX;
Alexei Filippove5197622018-08-18 03:04:331575 const color = this._entryColorsCache[entryIndex];
Blink Reformat4c46d092018-04-07 15:32:371576 const endBarX = this._timeToPositionClipped(entryEndTime);
1577 if (group.style.useDecoratorsForOverview && this._dataProvider.forceDecoration(entryIndex)) {
1578 const unclippedBarX = this._chartViewport.timeToPosition(entryStartTime);
1579 const barWidth = endBarX - barX;
1580 context.beginPath();
1581 context.fillStyle = color;
1582 context.fillRect(barX, y, barWidth, barHeight - 1);
1583 this._dataProvider.decorateEntry(
1584 entryIndex, context, '', barX, y, barWidth, barHeight, unclippedBarX, timeToPixel);
1585 continue;
1586 }
1587 range.append(new Common.Segment(barX, endBarX, color));
1588 }
1589 }
1590
1591 const segments = range.segments().slice().sort((a, b) => a.data.localeCompare(b.data));
1592 let lastColor;
1593 context.beginPath();
1594 for (let i = 0; i < segments.length; ++i) {
1595 const segment = segments[i];
1596 if (lastColor !== segments[i].data) {
1597 context.fill();
1598 context.beginPath();
1599 lastColor = segments[i].data;
1600 context.fillStyle = lastColor;
1601 }
Alexei Filippov23428d42018-04-28 01:09:071602 context.rect(segment.begin, y, segment.end - segment.begin, barHeight);
Blink Reformat4c46d092018-04-07 15:32:371603 }
1604 context.fill();
1605
1606 /**
1607 * @param {!Common.Segment} a
1608 * @param {!Common.Segment} b
1609 * @return {?Common.Segment}
1610 */
1611 function mergeCallback(a, b) {
1612 return a.data === b.data && a.end + 0.4 > b.end ? a : null;
1613 }
1614 }
1615
1616 /**
1617 * @param {!CanvasRenderingContext2D} context
1618 * @param {number} height
1619 * @param {number} width
1620 */
1621 _drawFlowEvents(context, width, height) {
1622 context.save();
1623 const ratio = window.devicePixelRatio;
1624 const top = this._chartViewport.scrollOffset();
1625 const arrowWidth = 6;
1626 context.scale(ratio, ratio);
1627 context.translate(0, -top);
1628
1629 context.fillStyle = '#7f5050';
1630 context.strokeStyle = '#7f5050';
1631 const td = this._timelineData();
1632 const endIndex = td.flowStartTimes.lowerBound(this._chartViewport.windowRightTime());
1633
1634 context.lineWidth = 0.5;
1635 for (let i = 0; i < endIndex; ++i) {
Tim van der Lippe1d6e57a2019-09-30 11:55:341636 if (!td.flowEndTimes[i] || td.flowEndTimes[i] < this._chartViewport.windowLeftTime()) {
Blink Reformat4c46d092018-04-07 15:32:371637 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341638 }
Blink Reformat4c46d092018-04-07 15:32:371639 const startX = this._chartViewport.timeToPosition(td.flowStartTimes[i]);
1640 const endX = this._chartViewport.timeToPosition(td.flowEndTimes[i]);
1641 const startLevel = td.flowStartLevels[i];
1642 const endLevel = td.flowEndLevels[i];
1643 const startY = this._levelToOffset(startLevel) + this._levelHeight(startLevel) / 2;
1644 const endY = this._levelToOffset(endLevel) + this._levelHeight(endLevel) / 2;
1645
1646
1647 const segment = Math.min((endX - startX) / 4, 40);
1648 const distanceTime = td.flowEndTimes[i] - td.flowStartTimes[i];
1649 const distanceY = (endY - startY) / 10;
1650 const spread = 30;
1651 const lineY = distanceTime < 1 ? startY : spread + Math.max(0, startY + distanceY * (i % spread));
1652
1653 const p = [];
1654 p.push({x: startX, y: startY});
1655 p.push({x: startX + arrowWidth, y: startY});
1656 p.push({x: startX + segment + 2 * arrowWidth, y: startY});
1657 p.push({x: startX + segment, y: lineY});
1658 p.push({x: startX + segment * 2, y: lineY});
1659 p.push({x: endX - segment * 2, y: lineY});
1660 p.push({x: endX - segment, y: lineY});
1661 p.push({x: endX - segment - 2 * arrowWidth, y: endY});
1662 p.push({x: endX - arrowWidth, y: endY});
1663
1664 context.beginPath();
1665 context.moveTo(p[0].x, p[0].y);
1666 context.lineTo(p[1].x, p[1].y);
1667 context.bezierCurveTo(p[2].x, p[2].y, p[3].x, p[3].y, p[4].x, p[4].y);
1668 context.lineTo(p[5].x, p[5].y);
1669 context.bezierCurveTo(p[6].x, p[6].y, p[7].x, p[7].y, p[8].x, p[8].y);
1670 context.stroke();
1671
1672 context.beginPath();
1673 context.arc(startX, startY, 2, -Math.PI / 2, Math.PI / 2, false);
1674 context.fill();
1675
1676 context.beginPath();
1677 context.moveTo(endX, endY);
1678 context.lineTo(endX - arrowWidth, endY - 3);
1679 context.lineTo(endX - arrowWidth, endY + 3);
1680 context.fill();
1681 }
1682 context.restore();
1683 }
1684
1685 _drawMarkers() {
1686 const markers = this._timelineData().markers;
1687 const left = this._markerIndexBeforeTime(this.minimumBoundary());
1688 const rightBoundary = this.maximumBoundary();
1689 const timeToPixel = this._chartViewport.timeToPixel();
1690
1691 const context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
1692 context.save();
1693 const ratio = window.devicePixelRatio;
1694 context.scale(ratio, ratio);
1695 context.translate(0, 3);
1696 const height = PerfUI.FlameChart.HeaderHeight - 1;
1697 for (let i = left; i < markers.length; i++) {
1698 const timestamp = markers[i].startTime();
Tim van der Lippe1d6e57a2019-09-30 11:55:341699 if (timestamp > rightBoundary) {
Blink Reformat4c46d092018-04-07 15:32:371700 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:341701 }
Blink Reformat4c46d092018-04-07 15:32:371702 markers[i].draw(context, this._chartViewport.timeToPosition(timestamp), height, timeToPixel);
1703 }
1704 context.restore();
1705 }
1706
1707 _updateMarkerHighlight() {
1708 const element = this._markerHighlighElement;
Tim van der Lippe1d6e57a2019-09-30 11:55:341709 if (element.parentElement) {
Blink Reformat4c46d092018-04-07 15:32:371710 element.remove();
Tim van der Lippe1d6e57a2019-09-30 11:55:341711 }
Blink Reformat4c46d092018-04-07 15:32:371712 const markerIndex = this._highlightedMarkerIndex;
Tim van der Lippe1d6e57a2019-09-30 11:55:341713 if (markerIndex === -1) {
Blink Reformat4c46d092018-04-07 15:32:371714 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341715 }
Blink Reformat4c46d092018-04-07 15:32:371716 const marker = this._timelineData().markers[markerIndex];
1717 const barX = this._timeToPositionClipped(marker.startTime());
1718 element.title = marker.title();
1719 const style = element.style;
1720 style.left = barX + 'px';
1721 style.backgroundColor = marker.color();
1722 this._viewportElement.appendChild(element);
1723 }
1724
1725 /**
1726 * @param {?PerfUI.FlameChart.TimelineData} timelineData
1727 */
1728 _processTimelineData(timelineData) {
1729 if (!timelineData) {
1730 this._timelineLevels = null;
1731 this._visibleLevelOffsets = null;
1732 this._visibleLevels = null;
1733 this._groupOffsets = null;
1734 this._rawTimelineData = null;
Alexei Filippov57ccafb2018-08-14 20:59:051735 this._forceDecorationCache = null;
Alexei Filippove5197622018-08-18 03:04:331736 this._entryColorsCache = null;
Blink Reformat4c46d092018-04-07 15:32:371737 this._rawTimelineDataLength = 0;
1738 this._selectedGroup = -1;
Anubha Mathur72dd5822019-06-13 23:05:191739 this._keyboardFocusedGroup = -1;
Blink Reformat4c46d092018-04-07 15:32:371740 this._flameChartDelegate.updateSelectedGroup(this, null);
1741 return;
1742 }
1743
1744 this._rawTimelineData = timelineData;
1745 this._rawTimelineDataLength = timelineData.entryStartTimes.length;
Alexei Filippov57ccafb2018-08-14 20:59:051746 this._forceDecorationCache = new Int8Array(this._rawTimelineDataLength);
Alexei Filippove5197622018-08-18 03:04:331747 this._entryColorsCache = new Array(this._rawTimelineDataLength);
1748 for (let i = 0; i < this._rawTimelineDataLength; ++i) {
Alexei Filippov57ccafb2018-08-14 20:59:051749 this._forceDecorationCache[i] = this._dataProvider.forceDecoration(i) ? 1 : 0;
Alexei Filippove5197622018-08-18 03:04:331750 this._entryColorsCache[i] = this._dataProvider.entryColor(i);
1751 }
Blink Reformat4c46d092018-04-07 15:32:371752
1753 const entryCounters = new Uint32Array(this._dataProvider.maxStackDepth() + 1);
Tim van der Lippe1d6e57a2019-09-30 11:55:341754 for (let i = 0; i < timelineData.entryLevels.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:371755 ++entryCounters[timelineData.entryLevels[i]];
Tim van der Lippe1d6e57a2019-09-30 11:55:341756 }
Blink Reformat4c46d092018-04-07 15:32:371757 const levelIndexes = new Array(entryCounters.length);
1758 for (let i = 0; i < levelIndexes.length; ++i) {
1759 levelIndexes[i] = new Uint32Array(entryCounters[i]);
1760 entryCounters[i] = 0;
1761 }
Anubha Mathur72dd5822019-06-13 23:05:191762
Blink Reformat4c46d092018-04-07 15:32:371763 for (let i = 0; i < timelineData.entryLevels.length; ++i) {
1764 const level = timelineData.entryLevels[i];
1765 levelIndexes[level][entryCounters[level]++] = i;
1766 }
1767 this._timelineLevels = levelIndexes;
1768 const groups = this._rawTimelineData.groups || [];
1769 for (let i = 0; i < groups.length; ++i) {
1770 const expanded = this._groupExpansionState[groups[i].name];
Tim van der Lippe1d6e57a2019-09-30 11:55:341771 if (expanded !== undefined) {
Blink Reformat4c46d092018-04-07 15:32:371772 groups[i].expanded = expanded;
Tim van der Lippe1d6e57a2019-09-30 11:55:341773 }
Blink Reformat4c46d092018-04-07 15:32:371774 }
1775 this._updateLevelPositions();
1776 this._updateHeight();
1777
1778 this._selectedGroup = timelineData.selectedGroup ? groups.indexOf(timelineData.selectedGroup) : -1;
Anubha Mathur72dd5822019-06-13 23:05:191779 this._keyboardFocusedGroup = this._selectedGroup;
Blink Reformat4c46d092018-04-07 15:32:371780 this._flameChartDelegate.updateSelectedGroup(this, timelineData.selectedGroup);
1781 }
1782
1783 _updateLevelPositions() {
1784 const levelCount = this._dataProvider.maxStackDepth();
1785 const groups = this._rawTimelineData.groups || [];
1786 this._visibleLevelOffsets = new Uint32Array(levelCount + 1);
1787 this._visibleLevelHeights = new Uint32Array(levelCount);
1788 this._visibleLevels = new Uint16Array(levelCount);
1789 this._groupOffsets = new Uint32Array(groups.length + 1);
1790
1791 let groupIndex = -1;
Alexei Filippov23428d42018-04-28 01:09:071792 let currentOffset = this._rulerEnabled ? PerfUI.FlameChart.HeaderHeight + 2 : 2;
Blink Reformat4c46d092018-04-07 15:32:371793 let visible = true;
1794 /** @type !Array<{nestingLevel: number, visible: boolean}> */
1795 const groupStack = [{nestingLevel: -1, visible: true}];
1796 const lastGroupLevel = Math.max(levelCount, groups.length ? groups.peekLast().startLevel + 1 : 0);
1797 let level;
1798 for (level = 0; level < lastGroupLevel; ++level) {
1799 let parentGroupIsVisible = true;
1800 let style;
1801 while (groupIndex < groups.length - 1 && level === groups[groupIndex + 1].startLevel) {
1802 ++groupIndex;
1803 style = groups[groupIndex].style;
1804 let nextLevel = true;
1805 while (groupStack.peekLast().nestingLevel >= style.nestingLevel) {
1806 groupStack.pop();
1807 nextLevel = false;
1808 }
1809 const thisGroupIsVisible =
1810 groupIndex >= 0 && this._isGroupCollapsible(groupIndex) ? groups[groupIndex].expanded : true;
1811 parentGroupIsVisible = groupStack.peekLast().visible;
1812 visible = thisGroupIsVisible && parentGroupIsVisible;
1813 groupStack.push({nestingLevel: style.nestingLevel, visible: visible});
Tim van der Lippe1d6e57a2019-09-30 11:55:341814 if (parentGroupIsVisible) {
Blink Reformat4c46d092018-04-07 15:32:371815 currentOffset += nextLevel ? 0 : style.padding;
Tim van der Lippe1d6e57a2019-09-30 11:55:341816 }
Blink Reformat4c46d092018-04-07 15:32:371817 this._groupOffsets[groupIndex] = currentOffset;
Tim van der Lippe1d6e57a2019-09-30 11:55:341818 if (parentGroupIsVisible && !style.shareHeaderLine) {
Blink Reformat4c46d092018-04-07 15:32:371819 currentOffset += style.height;
Tim van der Lippe1d6e57a2019-09-30 11:55:341820 }
Blink Reformat4c46d092018-04-07 15:32:371821 }
Tim van der Lippe1d6e57a2019-09-30 11:55:341822 if (level >= levelCount) {
Yang Guo8fb3ac52019-07-31 20:17:501823 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341824 }
Blink Reformat4c46d092018-04-07 15:32:371825 const isFirstOnLevel = groupIndex >= 0 && level === groups[groupIndex].startLevel;
1826 const thisLevelIsVisible =
1827 parentGroupIsVisible && (visible || isFirstOnLevel && groups[groupIndex].style.useFirstLineForOverview);
Yang Guo8fb3ac52019-07-31 20:17:501828 let height;
1829 if (groupIndex >= 0) {
1830 const group = groups[groupIndex];
1831 const styleB = group.style;
1832 height = isFirstOnLevel && !styleB.shareHeaderLine || (styleB.collapsible && !group.expanded) ?
1833 styleB.height :
1834 (styleB.itemsHeight || this._barHeight);
1835 } else {
1836 height = this._barHeight;
Blink Reformat4c46d092018-04-07 15:32:371837 }
Yang Guo8fb3ac52019-07-31 20:17:501838 this._visibleLevels[level] = thisLevelIsVisible;
1839 this._visibleLevelOffsets[level] = currentOffset;
1840 this._visibleLevelHeights[level] = height;
Tim van der Lippe1d6e57a2019-09-30 11:55:341841 if (thisLevelIsVisible || (parentGroupIsVisible && style && style.shareHeaderLine && isFirstOnLevel)) {
Blink Reformat4c46d092018-04-07 15:32:371842 currentOffset += this._visibleLevelHeights[level];
Tim van der Lippe1d6e57a2019-09-30 11:55:341843 }
Blink Reformat4c46d092018-04-07 15:32:371844 }
Tim van der Lippe1d6e57a2019-09-30 11:55:341845 if (groupIndex >= 0) {
Blink Reformat4c46d092018-04-07 15:32:371846 this._groupOffsets[groupIndex + 1] = currentOffset;
Tim van der Lippe1d6e57a2019-09-30 11:55:341847 }
Blink Reformat4c46d092018-04-07 15:32:371848 this._visibleLevelOffsets[level] = currentOffset;
Tim van der Lippe1d6e57a2019-09-30 11:55:341849 if (this._useWebGL) {
Alexei Filippov57ccafb2018-08-14 20:59:051850 this._setupGLGeometry();
Tim van der Lippe1d6e57a2019-09-30 11:55:341851 }
Blink Reformat4c46d092018-04-07 15:32:371852 }
1853
1854 /**
1855 * @param {number} index
1856 */
1857 _isGroupCollapsible(index) {
1858 const groups = this._rawTimelineData.groups || [];
1859 const style = groups[index].style;
Tim van der Lippe1d6e57a2019-09-30 11:55:341860 if (!style.shareHeaderLine || !style.collapsible) {
Blink Reformat4c46d092018-04-07 15:32:371861 return !!style.collapsible;
Tim van der Lippe1d6e57a2019-09-30 11:55:341862 }
Blink Reformat4c46d092018-04-07 15:32:371863 const isLastGroup = index + 1 >= groups.length;
Tim van der Lippe1d6e57a2019-09-30 11:55:341864 if (!isLastGroup && groups[index + 1].style.nestingLevel > style.nestingLevel) {
Blink Reformat4c46d092018-04-07 15:32:371865 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:341866 }
Blink Reformat4c46d092018-04-07 15:32:371867 const nextGroupLevel = isLastGroup ? this._dataProvider.maxStackDepth() : groups[index + 1].startLevel;
Tim van der Lippe1d6e57a2019-09-30 11:55:341868 if (nextGroupLevel !== groups[index].startLevel + 1) {
Blink Reformat4c46d092018-04-07 15:32:371869 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:341870 }
Blink Reformat4c46d092018-04-07 15:32:371871 // For groups that only have one line and share header line, pretend these are not collapsible
1872 // unless the itemsHeight does not match the headerHeight
1873 return style.height !== style.itemsHeight;
1874 }
1875
1876 /**
1877 * @param {number} entryIndex
1878 */
1879 setSelectedEntry(entryIndex) {
Tim van der Lippe1d6e57a2019-09-30 11:55:341880 if (this._selectedEntryIndex === entryIndex) {
Blink Reformat4c46d092018-04-07 15:32:371881 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341882 }
1883 if (entryIndex !== -1) {
Blink Reformat4c46d092018-04-07 15:32:371884 this._chartViewport.hideRangeSelection();
Tim van der Lippe1d6e57a2019-09-30 11:55:341885 }
Blink Reformat4c46d092018-04-07 15:32:371886 this._selectedEntryIndex = entryIndex;
1887 this._revealEntry(entryIndex);
1888 this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
1889 }
1890
1891 /**
1892 * @param {!Element} element
1893 * @param {number} entryIndex
1894 */
1895 _updateElementPosition(element, entryIndex) {
Alexei Filippov72d792d2018-11-06 07:15:041896 const elementMinWidthPx = 2;
1897 element.classList.add('hidden');
Tim van der Lippe1d6e57a2019-09-30 11:55:341898 if (entryIndex === -1) {
Blink Reformat4c46d092018-04-07 15:32:371899 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341900 }
Blink Reformat4c46d092018-04-07 15:32:371901 const timelineData = this._timelineData();
1902 const startTime = timelineData.entryStartTimes[entryIndex];
Alexei Filippov72d792d2018-11-06 07:15:041903 const duration = timelineData.entryTotalTimes[entryIndex];
Alexei Filippov65106502018-11-29 05:16:191904 let barX = 0;
1905 let barWidth = 0;
1906 let visible = true;
Alexei Filippov6c622e92018-11-10 02:13:591907 if (Number.isNaN(duration)) {
1908 const position = this._markerPositions.get(entryIndex);
Alexei Filippov65106502018-11-29 05:16:191909 if (position) {
1910 barX = position.x;
1911 barWidth = position.width;
1912 } else {
1913 visible = false;
1914 }
Alexei Filippov6c622e92018-11-10 02:13:591915 } else {
1916 barX = this._chartViewport.timeToPosition(startTime);
1917 barWidth = duration * this._chartViewport.timeToPixel();
1918 }
Tim van der Lippe1d6e57a2019-09-30 11:55:341919 if (barX + barWidth <= 0 || barX >= this._offsetWidth) {
Blink Reformat4c46d092018-04-07 15:32:371920 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341921 }
Blink Reformat4c46d092018-04-07 15:32:371922 const barCenter = barX + barWidth / 2;
1923 barWidth = Math.max(barWidth, elementMinWidthPx);
1924 barX = barCenter - barWidth / 2;
1925 const entryLevel = timelineData.entryLevels[entryIndex];
1926 const barY = this._levelToOffset(entryLevel) - this._chartViewport.scrollOffset();
1927 const barHeight = this._levelHeight(entryLevel);
1928 const style = element.style;
1929 style.left = barX + 'px';
1930 style.top = barY + 'px';
1931 style.width = barWidth + 'px';
1932 style.height = barHeight - 1 + 'px';
Alexei Filippov65106502018-11-29 05:16:191933 element.classList.toggle('hidden', !visible);
Blink Reformat4c46d092018-04-07 15:32:371934 this._viewportElement.appendChild(element);
1935 }
1936
1937 /**
1938 * @param {number} time
1939 * @return {number}
1940 */
1941 _timeToPositionClipped(time) {
1942 return Number.constrain(this._chartViewport.timeToPosition(time), 0, this._offsetWidth);
1943 }
1944
1945 /**
1946 * @param {number} level
1947 * @return {number}
1948 */
1949 _levelToOffset(level) {
1950 return this._visibleLevelOffsets[level];
1951 }
1952
1953 /**
1954 * @param {number} level
1955 * @return {number}
1956 */
1957 _levelHeight(level) {
1958 return this._visibleLevelHeights[level];
1959 }
1960
1961 _updateBoundaries() {
1962 this._totalTime = this._dataProvider.totalTime();
1963 this._minimumBoundary = this._dataProvider.minimumBoundary();
1964 this._chartViewport.setBoundaries(this._minimumBoundary, this._totalTime);
1965 }
1966
1967 _updateHeight() {
Alexei Filippov23428d42018-04-28 01:09:071968 const height = this._levelToOffset(this._dataProvider.maxStackDepth()) + 2;
Blink Reformat4c46d092018-04-07 15:32:371969 this._chartViewport.setContentHeight(height);
1970 }
1971
1972 /**
1973 * @override
1974 */
1975 onResize() {
1976 this.scheduleUpdate();
1977 }
1978
1979 /**
1980 * @override
1981 */
1982 update() {
Tim van der Lippe1d6e57a2019-09-30 11:55:341983 if (!this._timelineData()) {
Blink Reformat4c46d092018-04-07 15:32:371984 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341985 }
Blink Reformat4c46d092018-04-07 15:32:371986 this._resetCanvas();
1987 this._updateHeight();
1988 this._updateBoundaries();
1989 this._draw();
Tim van der Lippe1d6e57a2019-09-30 11:55:341990 if (!this._chartViewport.isDragging()) {
Blink Reformat4c46d092018-04-07 15:32:371991 this._updateHighlight();
Tim van der Lippe1d6e57a2019-09-30 11:55:341992 }
Blink Reformat4c46d092018-04-07 15:32:371993 }
1994
1995 reset() {
1996 this._chartViewport.reset();
1997 this._rawTimelineData = null;
1998 this._rawTimelineDataLength = 0;
1999 this._highlightedMarkerIndex = -1;
2000 this._highlightedEntryIndex = -1;
2001 this._selectedEntryIndex = -1;
2002 /** @type {!Map<string,!Map<string,number>>} */
2003 this._textWidth = new Map();
2004 this._chartViewport.scheduleUpdate();
2005 }
2006
2007 scheduleUpdate() {
2008 this._chartViewport.scheduleUpdate();
2009 }
2010
2011 _enabled() {
2012 return this._rawTimelineDataLength !== 0;
2013 }
2014
2015 /**
2016 * @override
2017 * @param {number} time
2018 * @return {number}
2019 */
2020 computePosition(time) {
2021 return this._chartViewport.timeToPosition(time);
2022 }
2023
2024 /**
2025 * @override
2026 * @param {number} value
2027 * @param {number=} precision
2028 * @return {string}
2029 */
2030 formatValue(value, precision) {
2031 return this._dataProvider.formatValue(value - this.zeroTime(), precision);
2032 }
2033
2034 /**
2035 * @override
2036 * @return {number}
2037 */
2038 maximumBoundary() {
2039 return this._chartViewport.windowRightTime();
2040 }
2041
2042 /**
2043 * @override
2044 * @return {number}
2045 */
2046 minimumBoundary() {
2047 return this._chartViewport.windowLeftTime();
2048 }
2049
2050 /**
2051 * @override
2052 * @return {number}
2053 */
2054 zeroTime() {
2055 return this._dataProvider.minimumBoundary();
2056 }
2057
2058 /**
2059 * @override
2060 * @return {number}
2061 */
2062 boundarySpan() {
Alexei Filippov2578eb02018-04-11 08:15:052063 return this.maximumBoundary() - this.minimumBoundary();
Blink Reformat4c46d092018-04-07 15:32:372064 }
2065};
2066
2067PerfUI.FlameChart.HeaderHeight = 15;
2068
2069PerfUI.FlameChart.MinimalTimeWindowMs = 0.5;
2070
2071/**
2072 * @interface
2073 */
2074PerfUI.FlameChartDataProvider = function() {};
2075
2076/**
2077 * @typedef {!{
2078 * name: string,
2079 * startLevel: number,
2080 * expanded: (boolean|undefined),
2081 * selectable: (boolean|undefined),
2082 * style: !PerfUI.FlameChart.GroupStyle
2083 * }}
2084 */
2085PerfUI.FlameChart.Group;
2086
2087/**
2088 * @typedef {!{
2089 * height: number,
2090 * padding: number,
2091 * collapsible: boolean,
2092 * font: string,
2093 * color: string,
2094 * backgroundColor: string,
2095 * nestingLevel: number,
2096 * itemsHeight: (number|undefined),
2097 * shareHeaderLine: (boolean|undefined),
2098 * useFirstLineForOverview: (boolean|undefined),
2099 * useDecoratorsForOverview: (boolean|undefined)
2100 * }}
2101 */
2102PerfUI.FlameChart.GroupStyle;
2103
2104/**
2105 * @unrestricted
2106 */
2107PerfUI.FlameChart.TimelineData = class {
2108 /**
2109 * @param {!Array<number>|!Uint16Array} entryLevels
2110 * @param {!Array<number>|!Float32Array} entryTotalTimes
2111 * @param {!Array<number>|!Float64Array} entryStartTimes
2112 * @param {?Array<!PerfUI.FlameChart.Group>} groups
2113 */
2114 constructor(entryLevels, entryTotalTimes, entryStartTimes, groups) {
2115 this.entryLevels = entryLevels;
2116 this.entryTotalTimes = entryTotalTimes;
2117 this.entryStartTimes = entryStartTimes;
2118 this.groups = groups;
2119 /** @type {!Array.<!PerfUI.FlameChartMarker>} */
2120 this.markers = [];
2121 this.flowStartTimes = [];
2122 this.flowStartLevels = [];
2123 this.flowEndTimes = [];
2124 this.flowEndLevels = [];
2125 /** @type {?PerfUI.FlameChart.Group} */
2126 this.selectedGroup = null;
2127 }
2128};
2129
2130PerfUI.FlameChartDataProvider.prototype = {
2131 /**
2132 * @return {number}
2133 */
2134 minimumBoundary() {},
2135
2136 /**
2137 * @return {number}
2138 */
2139 totalTime() {},
2140
2141 /**
2142 * @param {number} value
2143 * @param {number=} precision
2144 * @return {string}
2145 */
2146 formatValue(value, precision) {},
2147
2148 /**
2149 * @return {number}
2150 */
2151 maxStackDepth() {},
2152
2153 /**
2154 * @return {?PerfUI.FlameChart.TimelineData}
2155 */
2156 timelineData() {},
2157
2158 /**
2159 * @param {number} entryIndex
2160 * @return {?Element}
2161 */
2162 prepareHighlightedEntryInfo(entryIndex) {},
2163
2164 /**
2165 * @param {number} entryIndex
2166 * @return {boolean}
2167 */
2168 canJumpToEntry(entryIndex) {},
2169
2170 /**
2171 * @param {number} entryIndex
2172 * @return {?string}
2173 */
2174 entryTitle(entryIndex) {},
2175
2176 /**
2177 * @param {number} entryIndex
2178 * @return {?string}
2179 */
2180 entryFont(entryIndex) {},
2181
2182 /**
2183 * @param {number} entryIndex
2184 * @return {string}
2185 */
2186 entryColor(entryIndex) {},
2187
2188 /**
2189 * @param {number} entryIndex
2190 * @param {!CanvasRenderingContext2D} context
2191 * @param {?string} text
2192 * @param {number} barX
2193 * @param {number} barY
2194 * @param {number} barWidth
2195 * @param {number} barHeight
2196 * @param {number} unclippedBarX
2197 * @param {number} timeToPixelRatio
2198 * @return {boolean}
2199 */
2200 decorateEntry(entryIndex, context, text, barX, barY, barWidth, barHeight, unclippedBarX, timeToPixelRatio) {},
2201
2202 /**
2203 * @param {number} entryIndex
2204 * @return {boolean}
2205 */
2206 forceDecoration(entryIndex) {},
2207
2208 /**
2209 * @param {number} entryIndex
2210 * @return {string}
2211 */
2212 textColor(entryIndex) {},
2213};
2214
2215/**
2216 * @interface
2217 */
2218PerfUI.FlameChartMarker = function() {};
2219
2220PerfUI.FlameChartMarker.prototype = {
2221 /**
2222 * @return {number}
2223 */
2224 startTime() {},
2225
2226 /**
2227 * @return {string}
2228 */
2229 color() {},
2230
2231 /**
Alexei Filippov72d792d2018-11-06 07:15:042232 * @return {?string}
Blink Reformat4c46d092018-04-07 15:32:372233 */
2234 title() {},
2235
2236 /**
2237 * @param {!CanvasRenderingContext2D} context
2238 * @param {number} x
2239 * @param {number} height
2240 * @param {number} pixelsPerMillisecond
2241 */
2242 draw(context, x, height, pixelsPerMillisecond) {},
2243};
2244
2245/** @enum {symbol} */
2246PerfUI.FlameChart.Events = {
2247 EntrySelected: Symbol('EntrySelected'),
2248 EntryHighlighted: Symbol('EntryHighlighted')
2249};
2250
2251PerfUI.FlameChart.Colors = {
2252 SelectedGroupBackground: 'hsl(215, 85%, 98%)',
2253 SelectedGroupBorder: 'hsl(216, 68%, 54%)',
2254};