blob: 13982b4d03e6be01f01f654f71888a5d01d9dd71 [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
Jan Scheffler0aecd3a2020-07-30 07:37:1331// @ts-nocheck
32// TODO(crbug.com/1011811): Enable TypeScript compiler checks
33
Tim van der Lippebb769172020-02-12 15:32:4434import * as Common from '../common/common.js';
35import * as Host from '../host/host.js';
Jack Franklin1be909c2020-03-04 08:57:4136import * as Platform from '../platform/platform.js';
Paul Lewisead45752020-06-23 09:51:3637import * as SDK from '../sdk/sdk.js'; // eslint-disable-line no-unused-vars
Paul Lewisca569a52020-09-09 16:11:5138import * as ThemeSupport from '../theme_support/theme_support.js';
Tim van der Lippebb769172020-02-12 15:32:4439import * as UI from '../ui/ui.js';
40
Paul Lewis2cfa94e2020-01-13 10:30:1541import {ChartViewport, ChartViewportDelegate} from './ChartViewport.js'; // eslint-disable-line no-unused-vars
42import {Calculator, TimelineGrid} from './TimelineGrid.js'; // eslint-disable-line no-unused-vars
43
Blink Reformat4c46d092018-04-07 15:32:3744/**
45 * @interface
46 */
Tim van der Lippefd2b2ce2020-01-03 15:05:1847export class FlameChartDelegate {
Blink Reformat4c46d092018-04-07 15:32:3748 /**
49 * @param {number} startTime
50 * @param {number} endTime
51 * @param {boolean} animate
52 */
Tim van der Lippefd2b2ce2020-01-03 15:05:1853 windowChanged(startTime, endTime, animate) {
54 }
Blink Reformat4c46d092018-04-07 15:32:3755
56 /**
57 * @param {number} startTime
58 * @param {number} endTime
59 */
Tim van der Lippefd2b2ce2020-01-03 15:05:1860 updateRangeSelection(startTime, endTime) {
61 }
Blink Reformat4c46d092018-04-07 15:32:3762
63 /**
Tim van der Lippefd2b2ce2020-01-03 15:05:1864 * @param {!FlameChart} flameChart
Tim van der Lippe8ef250c2020-02-20 16:29:2565 * @param {?Group} group
Blink Reformat4c46d092018-04-07 15:32:3766 */
Tim van der Lippefd2b2ce2020-01-03 15:05:1867 updateSelectedGroup(flameChart, group) {
68 }
69}
Blink Reformat4c46d092018-04-07 15:32:3770
71/**
72 * @unrestricted
Paul Lewis2cfa94e2020-01-13 10:30:1573 * @implements {Calculator}
74 * @implements {ChartViewportDelegate}
Blink Reformat4c46d092018-04-07 15:32:3775 */
Tim van der Lippebb769172020-02-12 15:32:4476export class FlameChart extends UI.Widget.VBox {
Blink Reformat4c46d092018-04-07 15:32:3777 /**
Tim van der Lippefd2b2ce2020-01-03 15:05:1878 * @param {!FlameChartDataProvider} dataProvider
79 * @param {!FlameChartDelegate} flameChartDelegate
Tim van der Lippebb769172020-02-12 15:32:4480 * @param {!Common.Settings.Setting=} groupExpansionSetting
Blink Reformat4c46d092018-04-07 15:32:3781 */
82 constructor(dataProvider, flameChartDelegate, groupExpansionSetting) {
83 super(true);
84 this.registerRequiredCSS('perf_ui/flameChart.css');
85 this.contentElement.classList.add('flame-chart-main-pane');
86 this._groupExpansionSetting = groupExpansionSetting;
87 this._groupExpansionState = groupExpansionSetting && groupExpansionSetting.get() || {};
88 this._flameChartDelegate = flameChartDelegate;
89
Tim van der Lippe99e59b82019-09-30 20:00:5990 this._useWebGL = Root.Runtime.experiments.isEnabled('timelineWebGL');
Paul Lewis2cfa94e2020-01-13 10:30:1591 this._chartViewport = new ChartViewport(this);
Blink Reformat4c46d092018-04-07 15:32:3792 this._chartViewport.show(this.contentElement);
93
94 this._dataProvider = dataProvider;
95
Paul Lewis49b16822020-02-21 14:22:0396 this._candyStripeCanvas = /** @type {!HTMLCanvasElement} */ (document.createElement('canvas'));
97 this._createCandyStripePattern();
98
Blink Reformat4c46d092018-04-07 15:32:3799 this._viewportElement = this._chartViewport.viewportElement;
Alexei Filippov57ccafb2018-08-14 20:59:05100 if (this._useWebGL) {
101 this._canvasGL = /** @type {!HTMLCanvasElement} */ (this._viewportElement.createChild('canvas', 'fill'));
102 this._initWebGL();
103 }
104 this._canvas = /** @type {!HTMLCanvasElement} */ (this._viewportElement.createChild('canvas', 'fill'));
Blink Reformat4c46d092018-04-07 15:32:37105
Joel Einbinder83fc76e2018-06-11 23:19:47106 this._canvas.tabIndex = 0;
Anubha Mathur72dd5822019-06-13 23:05:19107 UI.ARIAUtils.setAccessibleName(this._canvas, ls`Flame Chart`);
108 UI.ARIAUtils.markAsTree(this._canvas);
Blink Reformat4c46d092018-04-07 15:32:37109 this.setDefaultFocusedElement(this._canvas);
Anubha Mathur72dd5822019-06-13 23:05:19110 this._canvas.classList.add('flame-chart-canvas');
Blink Reformat4c46d092018-04-07 15:32:37111 this._canvas.addEventListener('mousemove', this._onMouseMove.bind(this), false);
112 this._canvas.addEventListener('mouseout', this._onMouseOut.bind(this), false);
113 this._canvas.addEventListener('click', this._onClick.bind(this), false);
114 this._canvas.addEventListener('keydown', this._onKeyDown.bind(this), false);
115
116 this._entryInfo = this._viewportElement.createChild('div', 'flame-chart-entry-info');
117 this._markerHighlighElement = this._viewportElement.createChild('div', 'flame-chart-marker-highlight-element');
118 this._highlightElement = this._viewportElement.createChild('div', 'flame-chart-highlight-element');
119 this._selectedElement = this._viewportElement.createChild('div', 'flame-chart-selected-element');
Michael Liao712bbc22019-10-15 19:21:51120 this._canvas.addEventListener('focus', () => {
121 this._selectedElement.classList.remove('flame-chart-unfocused-selected-element');
Tim van der Lippefd2b2ce2020-01-03 15:05:18122 this.dispatchEventToListeners(Events.CanvasFocused);
Michael Liao712bbc22019-10-15 19:21:51123 }, false);
124 this._canvas.addEventListener('blur', () => {
125 this._selectedElement.classList.add('flame-chart-unfocused-selected-element');
126 }, false);
Blink Reformat4c46d092018-04-07 15:32:37127
Tim van der Lippebb769172020-02-12 15:32:44128 UI.UIUtils.installDragHandle(
Blink Reformat4c46d092018-04-07 15:32:37129 this._viewportElement, this._startDragging.bind(this), this._dragging.bind(this), this._endDragging.bind(this),
130 null);
131
132 this._rulerEnabled = true;
133 this._rangeSelectionStart = 0;
134 this._rangeSelectionEnd = 0;
135 this._barHeight = 17;
136 this._textBaseline = 5;
137 this._textPadding = 5;
138 this._markerRadius = 6;
139 this._chartViewport.setWindowTimes(
140 dataProvider.minimumBoundary(), dataProvider.minimumBoundary() + dataProvider.totalTime());
141
142 /** @const */
143 this._headerLeftPadding = 6;
144 /** @const */
145 this._arrowSide = 8;
146 /** @const */
147 this._expansionArrowIndent = this._headerLeftPadding + this._arrowSide / 2;
148 /** @const */
149 this._headerLabelXPadding = 3;
150 /** @const */
151 this._headerLabelYPadding = 2;
152
153 this._highlightedMarkerIndex = -1;
154 this._highlightedEntryIndex = -1;
155 this._selectedEntryIndex = -1;
156 this._rawTimelineDataLength = 0;
Alexei Filippov72d792d2018-11-06 07:15:04157 /** @type {!Map<string, !Map<string,number>>} */
Blink Reformat4c46d092018-04-07 15:32:37158 this._textWidth = new Map();
Alexei Filippov6c622e92018-11-10 02:13:59159 /** @type {!Map<number, !{x: number, width: number}>} */
160 this._markerPositions = new Map();
Blink Reformat4c46d092018-04-07 15:32:37161
162 this._lastMouseOffsetX = 0;
163 this._selectedGroup = -1;
Anubha Mathur72dd5822019-06-13 23:05:19164
165 // Keyboard focused group is used to navigate groups irrespective of whether they are selectable or not
166 this._keyboardFocusedGroup = -1;
167
Paul Lewisca569a52020-09-09 16:11:51168 this._selectedGroupBackroundColor = ThemeSupport.ThemeSupport.instance().patchColorText(
169 Colors.SelectedGroupBackground, ThemeSupport.ThemeSupport.ColorUsage.Background);
170 this._selectedGroupBorderColor = ThemeSupport.ThemeSupport.instance().patchColorText(
171 Colors.SelectedGroupBorder, ThemeSupport.ThemeSupport.ColorUsage.Background);
Blink Reformat4c46d092018-04-07 15:32:37172 }
173
174 /**
175 * @override
176 */
177 willHide() {
178 this.hideHighlight();
179 }
180
181 /**
182 * @param {number} value
183 */
184 setBarHeight(value) {
185 this._barHeight = value;
186 }
187
188 /**
189 * @param {number} value
190 */
191 setTextBaseline(value) {
192 this._textBaseline = value;
193 }
194
195 /**
196 * @param {number} value
197 */
198 setTextPadding(value) {
199 this._textPadding = value;
200 }
201
202 /**
203 * @param {boolean} enable
204 */
205 enableRuler(enable) {
206 this._rulerEnabled = enable;
207 }
208
209 alwaysShowVerticalScroll() {
210 this._chartViewport.alwaysShowVerticalScroll();
211 }
212
213 disableRangeSelection() {
214 this._chartViewport.disableRangeSelection();
215 }
216
217 /**
218 * @param {number} entryIndex
219 */
220 highlightEntry(entryIndex) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34221 if (this._highlightedEntryIndex === entryIndex) {
Blink Reformat4c46d092018-04-07 15:32:37222 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34223 }
224 if (!this._dataProvider.entryColor(entryIndex)) {
Blink Reformat4c46d092018-04-07 15:32:37225 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34226 }
Blink Reformat4c46d092018-04-07 15:32:37227 this._highlightedEntryIndex = entryIndex;
228 this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
Tim van der Lippefd2b2ce2020-01-03 15:05:18229 this.dispatchEventToListeners(Events.EntryHighlighted, entryIndex);
Blink Reformat4c46d092018-04-07 15:32:37230 }
231
232 hideHighlight() {
233 this._entryInfo.removeChildren();
234 this._highlightedEntryIndex = -1;
235 this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
Tim van der Lippefd2b2ce2020-01-03 15:05:18236 this.dispatchEventToListeners(Events.EntryHighlighted, -1);
Blink Reformat4c46d092018-04-07 15:32:37237 }
238
Paul Lewis49b16822020-02-21 14:22:03239 _createCandyStripePattern() {
240 // Set the candy stripe pattern to 17px so it repeats well.
241 const size = 17;
242 this._candyStripeCanvas.width = size;
243 this._candyStripeCanvas.height = size;
244
245 const ctx = this._candyStripeCanvas.getContext('2d');
246
247 // Rotate the stripe by 45deg to the right.
248 ctx.translate(size * 0.5, size * 0.5);
249 ctx.rotate(Math.PI * 0.25);
250 ctx.translate(-size * 0.5, -size * 0.5);
251
252 ctx.fillStyle = 'rgba(255, 0, 0, 0.4)';
253 for (let x = -size; x < size * 2; x += 3) {
254 ctx.fillRect(x, -size, 1, size * 3);
255 }
256 }
257
Blink Reformat4c46d092018-04-07 15:32:37258 _resetCanvas() {
259 const ratio = window.devicePixelRatio;
260 const width = Math.round(this._offsetWidth * ratio);
261 const height = Math.round(this._offsetHeight * ratio);
262 this._canvas.width = width;
263 this._canvas.height = height;
264 this._canvas.style.width = `${width / ratio}px`;
265 this._canvas.style.height = `${height / ratio}px`;
Alexei Filippov57ccafb2018-08-14 20:59:05266 if (this._useWebGL) {
267 this._canvasGL.width = width;
268 this._canvasGL.height = height;
269 this._canvasGL.style.width = `${width / ratio}px`;
270 this._canvasGL.style.height = `${height / ratio}px`;
271 }
Blink Reformat4c46d092018-04-07 15:32:37272 }
273
274 /**
275 * @override
276 * @param {number} startTime
277 * @param {number} endTime
278 * @param {boolean} animate
279 */
Alexei Filippov2578eb02018-04-11 08:15:05280 windowChanged(startTime, endTime, animate) {
281 this._flameChartDelegate.windowChanged(startTime, endTime, animate);
Blink Reformat4c46d092018-04-07 15:32:37282 }
283
284 /**
285 * @override
286 * @param {number} startTime
287 * @param {number} endTime
288 */
289 updateRangeSelection(startTime, endTime) {
290 this._flameChartDelegate.updateRangeSelection(startTime, endTime);
291 }
292
293 /**
294 * @override
295 * @param {number} width
296 * @param {number} height
297 */
298 setSize(width, height) {
299 this._offsetWidth = width;
300 this._offsetHeight = height;
301 }
302
303 /**
304 * @param {!MouseEvent} event
305 */
306 _startDragging(event) {
307 this.hideHighlight();
308 this._maxDragOffset = 0;
309 this._dragStartX = event.pageX;
310 this._dragStartY = event.pageY;
311 return true;
312 }
313
314 /**
315 * @param {!MouseEvent} event
316 */
317 _dragging(event) {
318 const dx = event.pageX - this._dragStartX;
319 const dy = event.pageY - this._dragStartY;
320 this._maxDragOffset = Math.max(this._maxDragOffset, Math.sqrt(dx * dx + dy * dy));
321 }
322
323 /**
324 * @param {!MouseEvent} event
325 */
326 _endDragging(event) {
327 this._updateHighlight();
328 }
329
330 /**
Tim van der Lippefd2b2ce2020-01-03 15:05:18331 * @return {?TimelineData}
Blink Reformat4c46d092018-04-07 15:32:37332 */
333 _timelineData() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34334 if (!this._dataProvider) {
Blink Reformat4c46d092018-04-07 15:32:37335 return null;
Tim van der Lippe1d6e57a2019-09-30 11:55:34336 }
Blink Reformat4c46d092018-04-07 15:32:37337 const timelineData = this._dataProvider.timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:34338 if (timelineData !== this._rawTimelineData || timelineData.entryStartTimes.length !== this._rawTimelineDataLength) {
Blink Reformat4c46d092018-04-07 15:32:37339 this._processTimelineData(timelineData);
Tim van der Lippe1d6e57a2019-09-30 11:55:34340 }
Blink Reformat4c46d092018-04-07 15:32:37341 return this._rawTimelineData;
342 }
343
344 /**
345 * @param {number} entryIndex
346 */
347 _revealEntry(entryIndex) {
348 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:34349 if (!timelineData) {
Blink Reformat4c46d092018-04-07 15:32:37350 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34351 }
Blink Reformat4c46d092018-04-07 15:32:37352 const timeLeft = this._chartViewport.windowLeftTime();
353 const timeRight = this._chartViewport.windowRightTime();
354 const entryStartTime = timelineData.entryStartTimes[entryIndex];
355 const entryTotalTime = timelineData.entryTotalTimes[entryIndex];
356 const entryEndTime = entryStartTime + entryTotalTime;
357 let minEntryTimeWindow = Math.min(entryTotalTime, timeRight - timeLeft);
358
359 const level = timelineData.entryLevels[entryIndex];
360 this._chartViewport.setScrollOffset(this._levelToOffset(level), this._levelHeight(level));
361
362 const minVisibleWidthPx = 30;
363 const futurePixelToTime = (timeRight - timeLeft) / this._offsetWidth;
364 minEntryTimeWindow = Math.max(minEntryTimeWindow, futurePixelToTime * minVisibleWidthPx);
365 if (timeLeft > entryEndTime) {
366 const delta = timeLeft - entryEndTime + minEntryTimeWindow;
Alexei Filippov2578eb02018-04-11 08:15:05367 this.windowChanged(timeLeft - delta, timeRight - delta, /* animate */ true);
Blink Reformat4c46d092018-04-07 15:32:37368 } else if (timeRight < entryStartTime) {
369 const delta = entryStartTime - timeRight + minEntryTimeWindow;
Alexei Filippov2578eb02018-04-11 08:15:05370 this.windowChanged(timeLeft + delta, timeRight + delta, /* animate */ true);
Blink Reformat4c46d092018-04-07 15:32:37371 }
372 }
373
374 /**
375 * @param {number} startTime
376 * @param {number} endTime
377 * @param {boolean=} animate
378 */
379 setWindowTimes(startTime, endTime, animate) {
380 this._chartViewport.setWindowTimes(startTime, endTime, animate);
381 this._updateHighlight();
382 }
383
384 /**
385 * @param {!Event} event
386 */
387 _onMouseMove(event) {
388 this._lastMouseOffsetX = event.offsetX;
389 this._lastMouseOffsetY = event.offsetY;
Tim van der Lippe1d6e57a2019-09-30 11:55:34390 if (!this._enabled()) {
Blink Reformat4c46d092018-04-07 15:32:37391 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34392 }
393 if (this._chartViewport.isDragging()) {
Blink Reformat4c46d092018-04-07 15:32:37394 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34395 }
Blink Reformat4c46d092018-04-07 15:32:37396 if (this._coordinatesToGroupIndex(event.offsetX, event.offsetY, true /* headerOnly */) >= 0) {
397 this.hideHighlight();
398 this._viewportElement.style.cursor = 'pointer';
399 return;
400 }
401 this._updateHighlight();
402 }
403
404 _updateHighlight() {
Alexei Filippov8ee66382018-11-30 01:53:56405 const entryIndex = this._coordinatesToEntryIndex(this._lastMouseOffsetX, this._lastMouseOffsetY);
Blink Reformat4c46d092018-04-07 15:32:37406 if (entryIndex === -1) {
407 this.hideHighlight();
408 const group =
409 this._coordinatesToGroupIndex(this._lastMouseOffsetX, this._lastMouseOffsetY, false /* headerOnly */);
Tim van der Lippe1d6e57a2019-09-30 11:55:34410 if (group >= 0 && this._rawTimelineData.groups[group].selectable) {
Blink Reformat4c46d092018-04-07 15:32:37411 this._viewportElement.style.cursor = 'pointer';
Tim van der Lippe1d6e57a2019-09-30 11:55:34412 } else {
Blink Reformat4c46d092018-04-07 15:32:37413 this._viewportElement.style.cursor = 'default';
Tim van der Lippe1d6e57a2019-09-30 11:55:34414 }
Blink Reformat4c46d092018-04-07 15:32:37415 return;
416 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34417 if (this._chartViewport.isDragging()) {
Blink Reformat4c46d092018-04-07 15:32:37418 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34419 }
Blink Reformat4c46d092018-04-07 15:32:37420 this._updatePopover(entryIndex);
421 this._viewportElement.style.cursor = this._dataProvider.canJumpToEntry(entryIndex) ? 'pointer' : 'default';
422 this.highlightEntry(entryIndex);
423 }
424
425 _onMouseOut() {
426 this._lastMouseOffsetX = -1;
427 this._lastMouseOffsetY = -1;
428 this.hideHighlight();
429 }
430
431 /**
432 * @param {number} entryIndex
433 */
434 _updatePopover(entryIndex) {
435 if (entryIndex === this._highlightedEntryIndex) {
436 this._updatePopoverOffset();
437 return;
438 }
439 this._entryInfo.removeChildren();
440 const popoverElement = this._dataProvider.prepareHighlightedEntryInfo(entryIndex);
441 if (popoverElement) {
442 this._entryInfo.appendChild(popoverElement);
443 this._updatePopoverOffset();
444 }
445 }
446
447 _updatePopoverOffset() {
448 const mouseX = this._lastMouseOffsetX;
449 const mouseY = this._lastMouseOffsetY;
450 const parentWidth = this._entryInfo.parentElement.clientWidth;
451 const parentHeight = this._entryInfo.parentElement.clientHeight;
452 const infoWidth = this._entryInfo.clientWidth;
453 const infoHeight = this._entryInfo.clientHeight;
454 const /** @const */ offsetX = 10;
455 const /** @const */ offsetY = 6;
456 let x;
457 let y;
458 for (let quadrant = 0; quadrant < 4; ++quadrant) {
459 const dx = quadrant & 2 ? -offsetX - infoWidth : offsetX;
460 const dy = quadrant & 1 ? -offsetY - infoHeight : offsetY;
Jack Franklin1be909c2020-03-04 08:57:41461 x = Platform.NumberUtilities.clamp(mouseX + dx, 0, parentWidth - infoWidth);
462 y = Platform.NumberUtilities.clamp(mouseY + dy, 0, parentHeight - infoHeight);
Tim van der Lippe1d6e57a2019-09-30 11:55:34463 if (x >= mouseX || mouseX >= x + infoWidth || y >= mouseY || mouseY >= y + infoHeight) {
Blink Reformat4c46d092018-04-07 15:32:37464 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:34465 }
Blink Reformat4c46d092018-04-07 15:32:37466 }
467 this._entryInfo.style.left = x + 'px';
468 this._entryInfo.style.top = y + 'px';
469 }
470
471 /**
472 * @param {!Event} event
473 */
474 _onClick(event) {
475 this.focus();
476 // onClick comes after dragStart and dragEnd events.
477 // So if there was drag (mouse move) in the middle of that events
478 // we skip the click. Otherwise we jump to the sources.
479 const /** @const */ clickThreshold = 5;
Tim van der Lippe1d6e57a2019-09-30 11:55:34480 if (this._maxDragOffset > clickThreshold) {
Blink Reformat4c46d092018-04-07 15:32:37481 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34482 }
Blink Reformat4c46d092018-04-07 15:32:37483
484 this._selectGroup(this._coordinatesToGroupIndex(event.offsetX, event.offsetY, false /* headerOnly */));
Anubha Mathur72dd5822019-06-13 23:05:19485 this._toggleGroupExpand(this._coordinatesToGroupIndex(event.offsetX, event.offsetY, true /* headerOnly */));
Blink Reformat4c46d092018-04-07 15:32:37486 const timelineData = this._timelineData();
487 if (event.shiftKey && this._highlightedEntryIndex !== -1 && timelineData) {
488 const start = timelineData.entryStartTimes[this._highlightedEntryIndex];
489 const end = start + timelineData.entryTotalTimes[this._highlightedEntryIndex];
490 this._chartViewport.setRangeSelection(start, end);
491 } else {
492 this._chartViewport.onClick(event);
Tim van der Lippefd2b2ce2020-01-03 15:05:18493 this.dispatchEventToListeners(Events.EntryInvoked, this._highlightedEntryIndex);
Blink Reformat4c46d092018-04-07 15:32:37494 }
495 }
496
497 /**
498 * @param {number} groupIndex
499 */
500 _selectGroup(groupIndex) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34501 if (groupIndex < 0 || this._selectedGroup === groupIndex) {
Blink Reformat4c46d092018-04-07 15:32:37502 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34503 }
Anubha Mathur72dd5822019-06-13 23:05:19504 const groups = this._rawTimelineData.groups;
505 this._keyboardFocusedGroup = groupIndex;
Anubha Mathur430a80f2019-12-12 00:26:11506 this._scrollGroupIntoView(groupIndex);
Michael Liao0bad0c32020-01-02 18:51:04507 const groupName = groups[groupIndex].name;
Anubha Mathur72dd5822019-06-13 23:05:19508 if (!groups[groupIndex].selectable) {
509 this._deselectAllGroups();
Michael Liao0bad0c32020-01-02 18:51:04510 UI.ARIAUtils.alert(ls`${groupName} hovered`, this._canvas);
Anubha Mathur72dd5822019-06-13 23:05:19511 } else {
512 this._selectedGroup = groupIndex;
513 this._flameChartDelegate.updateSelectedGroup(this, groups[groupIndex]);
514 this._resetCanvas();
515 this._draw();
Michael Liao (WPT)b54514b2019-08-16 23:11:53516 UI.ARIAUtils.alert(ls`${groupName} selected`, this._canvas);
Anubha Mathur72dd5822019-06-13 23:05:19517 }
518 }
Blink Reformat4c46d092018-04-07 15:32:37519
Anubha Mathur72dd5822019-06-13 23:05:19520 _deselectAllGroups() {
521 this._selectedGroup = -1;
522 this._flameChartDelegate.updateSelectedGroup(this, null);
523 this._resetCanvas();
524 this._draw();
525 }
526
527 _deselectAllEntries() {
528 this._selectedEntryIndex = -1;
Blink Reformat4c46d092018-04-07 15:32:37529 this._resetCanvas();
530 this._draw();
531 }
532
533 /**
Anubha Mathur72dd5822019-06-13 23:05:19534 * @param {number} index
535 */
536 _isGroupFocused(index) {
537 return index === this._selectedGroup || index === this._keyboardFocusedGroup;
538 }
539
540 /**
Anubha Mathur430a80f2019-12-12 00:26:11541 * @param {number} index
542 */
543 _scrollGroupIntoView(index) {
544 if (index < 0) {
545 return;
546 }
547
548 const groups = this._rawTimelineData.groups;
549 const groupOffsets = this._groupOffsets;
550 const groupTop = groupOffsets[index];
551
552 let nextOffset = groupOffsets[index + 1];
553 if (index === groups.length - 1) {
554 nextOffset += groups[index].style.padding;
555 }
556
557 // For the top group, scroll all the way to the top of the chart
558 // to accommodate the bar with time markers
559 const scrollTop = index === 0 ? 0 : groupTop;
560
561 const scrollHeight = Math.min(nextOffset - scrollTop, this._chartViewport.chartHeight());
562 this._chartViewport.setScrollOffset(scrollTop, scrollHeight);
563 }
564
565 /**
Blink Reformat4c46d092018-04-07 15:32:37566 * @param {number} groupIndex
567 */
Anubha Mathur72dd5822019-06-13 23:05:19568 _toggleGroupExpand(groupIndex) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34569 if (groupIndex < 0 || !this._isGroupCollapsible(groupIndex)) {
Anubha Mathur72dd5822019-06-13 23:05:19570 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34571 }
Anubha Mathur72dd5822019-06-13 23:05:19572
573 this._expandGroup(groupIndex, !this._rawTimelineData.groups[groupIndex].expanded /* setExpanded */);
574 }
575
576 /**
577 * @param {number} groupIndex
578 * @param {boolean=} setExpanded
Michael Liao (WPT)b54514b2019-08-16 23:11:53579 * @param {boolean=} propagatedExpand
Anubha Mathur72dd5822019-06-13 23:05:19580 */
Michael Liao (WPT)b54514b2019-08-16 23:11:53581 _expandGroup(groupIndex, setExpanded = true, propagatedExpand = false) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34582 if (groupIndex < 0 || !this._isGroupCollapsible(groupIndex)) {
Blink Reformat4c46d092018-04-07 15:32:37583 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34584 }
Blink Reformat4c46d092018-04-07 15:32:37585
586 const groups = this._rawTimelineData.groups;
587 const group = groups[groupIndex];
Anubha Mathur72dd5822019-06-13 23:05:19588 group.expanded = setExpanded;
589
Blink Reformat4c46d092018-04-07 15:32:37590 this._groupExpansionState[group.name] = group.expanded;
Tim van der Lippe1d6e57a2019-09-30 11:55:34591 if (this._groupExpansionSetting) {
Blink Reformat4c46d092018-04-07 15:32:37592 this._groupExpansionSetting.set(this._groupExpansionState);
Tim van der Lippe1d6e57a2019-09-30 11:55:34593 }
Blink Reformat4c46d092018-04-07 15:32:37594 this._updateLevelPositions();
595
596 this._updateHighlight();
597 if (!group.expanded) {
598 const timelineData = this._timelineData();
599 const level = timelineData.entryLevels[this._selectedEntryIndex];
600 if (this._selectedEntryIndex >= 0 && level >= group.startLevel &&
Tim van der Lippe1d6e57a2019-09-30 11:55:34601 (groupIndex >= groups.length - 1 || groups[groupIndex + 1].startLevel > level)) {
Blink Reformat4c46d092018-04-07 15:32:37602 this._selectedEntryIndex = -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34603 }
Blink Reformat4c46d092018-04-07 15:32:37604 }
605
606 this._updateHeight();
607 this._resetCanvas();
608 this._draw();
Michael Liao (WPT)b54514b2019-08-16 23:11:53609
Anubha Mathur430a80f2019-12-12 00:26:11610 this._scrollGroupIntoView(groupIndex);
Michael Liao (WPT)b54514b2019-08-16 23:11:53611 // We only want to read expanded/collapsed state on user inputted expand/collapse
612 if (!propagatedExpand) {
613 const groupName = groups[groupIndex].name;
614 const content = group.expanded ? ls`${groupName} expanded` : ls`${groupName} collapsed`;
615 UI.ARIAUtils.alert(content, this._canvas);
616 }
Blink Reformat4c46d092018-04-07 15:32:37617 }
618
619 /**
620 * @param {!Event} e
621 */
622 _onKeyDown(e) {
Tim van der Lippebb769172020-02-12 15:32:44623 if (!UI.KeyboardShortcut.KeyboardShortcut.hasNoModifiers(e) || !this._timelineData()) {
Anubha Mathur72dd5822019-06-13 23:05:19624 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34625 }
Anubha Mathur72dd5822019-06-13 23:05:19626
627 const eventHandled = this._handleSelectionNavigation(e);
628
629 // Handle keyboard navigation in groups
Tim van der Lippe1d6e57a2019-09-30 11:55:34630 if (!eventHandled && this._rawTimelineData && this._rawTimelineData.groups) {
Anubha Mathur72dd5822019-06-13 23:05:19631 this._handleKeyboardGroupNavigation(e);
Tim van der Lippe1d6e57a2019-09-30 11:55:34632 }
Blink Reformat4c46d092018-04-07 15:32:37633 }
634
635 /**
Ted Meyerb0928d02020-06-23 05:12:45636 * @param {string} eventName
637 * @param {function(!Event):undefined} onEvent
638 */
639 bindCanvasEvent(eventName, onEvent) {
640 this._canvas.addEventListener(eventName, onEvent);
641 }
642
643 /**
Blink Reformat4c46d092018-04-07 15:32:37644 * @param {!Event} e
645 */
Anubha Mathur72dd5822019-06-13 23:05:19646 _handleKeyboardGroupNavigation(e) {
647 let handled = false;
648 let entrySelected = false;
649
650 if (e.code === 'ArrowUp') {
651 handled = this._selectPreviousGroup();
652 } else if (e.code === 'ArrowDown') {
653 handled = this._selectNextGroup();
654 } else if (e.code === 'ArrowLeft') {
655 if (this._keyboardFocusedGroup >= 0) {
656 this._expandGroup(this._keyboardFocusedGroup, false /* setExpanded */);
657 handled = true;
658 }
659 } else if (e.code === 'ArrowRight') {
660 if (this._keyboardFocusedGroup >= 0) {
661 this._expandGroup(this._keyboardFocusedGroup, true /* setExpanded */);
662 this._selectFirstChild();
663 handled = true;
664 }
665 } else if (isEnterKey(e)) {
666 entrySelected = this._selectFirstEntryInCurrentGroup();
667 handled = entrySelected;
668 }
669
Tim van der Lippe1d6e57a2019-09-30 11:55:34670 if (handled && !entrySelected) {
Anubha Mathur72dd5822019-06-13 23:05:19671 this._deselectAllEntries();
Tim van der Lippe1d6e57a2019-09-30 11:55:34672 }
Anubha Mathur72dd5822019-06-13 23:05:19673
Tim van der Lippe1d6e57a2019-09-30 11:55:34674 if (handled) {
Anubha Mathur72dd5822019-06-13 23:05:19675 e.consume(true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34676 }
Anubha Mathur72dd5822019-06-13 23:05:19677 }
678
679 /**
680 * @return {boolean}
681 */
682 _selectFirstEntryInCurrentGroup() {
683 const allGroups = this._rawTimelineData.groups;
684
Tim van der Lippe1d6e57a2019-09-30 11:55:34685 if (this._keyboardFocusedGroup < 0) {
Anubha Mathur72dd5822019-06-13 23:05:19686 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34687 }
Anubha Mathur72dd5822019-06-13 23:05:19688
689 const group = allGroups[this._keyboardFocusedGroup];
690 const startLevelInGroup = group.startLevel;
691
692 // Return if no levels in this group
Tim van der Lippe1d6e57a2019-09-30 11:55:34693 if (startLevelInGroup < 0) {
Anubha Mathur72dd5822019-06-13 23:05:19694 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34695 }
Anubha Mathur72dd5822019-06-13 23:05:19696
697 // Make sure this is the innermost nested group with this startLevel
698 // This is because a parent group also contains levels of all its child groups
699 // So check if the next group has the same level, if it does, user should
700 // go to that child group to select this entry
701 if (this._keyboardFocusedGroup < allGroups.length - 1 &&
Tim van der Lippe1d6e57a2019-09-30 11:55:34702 allGroups[this._keyboardFocusedGroup + 1].startLevel === startLevelInGroup) {
Anubha Mathur72dd5822019-06-13 23:05:19703 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34704 }
Anubha Mathur72dd5822019-06-13 23:05:19705
706
707 // Get first (default) entry in startLevel of selected group
708 const firstEntryIndex = this._timelineLevels[startLevelInGroup][0];
709
710 this._expandGroup(this._keyboardFocusedGroup, true /* setExpanded */);
711 this.setSelectedEntry(firstEntryIndex);
712 return true;
713 }
714
715 /**
716 * @return {boolean}
717 */
718 _selectPreviousGroup() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34719 if (this._keyboardFocusedGroup <= 0) {
Anubha Mathur72dd5822019-06-13 23:05:19720 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34721 }
Anubha Mathur72dd5822019-06-13 23:05:19722
723 const groupIndexToSelect = this._getGroupIndexToSelect(-1 /* offset */);
724 this._selectGroup(groupIndexToSelect);
725 return true;
726 }
727
728 /**
729 * @return {boolean}
730 */
731 _selectNextGroup() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34732 if (this._keyboardFocusedGroup >= this._rawTimelineData.groups.length - 1) {
Anubha Mathur72dd5822019-06-13 23:05:19733 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34734 }
Anubha Mathur72dd5822019-06-13 23:05:19735
736 const groupIndexToSelect = this._getGroupIndexToSelect(1 /* offset */);
737 this._selectGroup(groupIndexToSelect);
738 return true;
739 }
740
741 /**
742 * @param {number} offset
743 * @return {number}
744 */
745 _getGroupIndexToSelect(offset) {
746 const allGroups = this._rawTimelineData.groups;
747 let groupIndexToSelect = this._keyboardFocusedGroup;
748 let groupName, groupWithSubNestingLevel;
749
750 do {
751 groupIndexToSelect += offset;
752 groupName = this._rawTimelineData.groups[groupIndexToSelect].name;
753 groupWithSubNestingLevel = this._keyboardFocusedGroup !== -1 &&
754 allGroups[groupIndexToSelect].style.nestingLevel > allGroups[this._keyboardFocusedGroup].style.nestingLevel;
755 } while (groupIndexToSelect > 0 && groupIndexToSelect < allGroups.length - 1 &&
756 (!groupName || groupWithSubNestingLevel));
757
758 return groupIndexToSelect;
759 }
760
761 _selectFirstChild() {
762 const allGroups = this._rawTimelineData.groups;
Tim van der Lippe1d6e57a2019-09-30 11:55:34763 if (this._keyboardFocusedGroup < 0 || this._keyboardFocusedGroup >= allGroups.length - 1) {
Anubha Mathur72dd5822019-06-13 23:05:19764 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34765 }
Anubha Mathur72dd5822019-06-13 23:05:19766
767 const groupIndexToSelect = this._keyboardFocusedGroup + 1;
768 if (allGroups[groupIndexToSelect].style.nestingLevel > allGroups[this._keyboardFocusedGroup].style.nestingLevel) {
769 this._selectGroup(groupIndexToSelect);
Anubha Mathur72dd5822019-06-13 23:05:19770 }
771 }
772
773 /**
774 * @param {!Event} e
775 * @return {boolean}
776 */
Blink Reformat4c46d092018-04-07 15:32:37777 _handleSelectionNavigation(e) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34778 if (this._selectedEntryIndex === -1) {
Anubha Mathur72dd5822019-06-13 23:05:19779 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34780 }
Blink Reformat4c46d092018-04-07 15:32:37781 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:34782 if (!timelineData) {
Anubha Mathur72dd5822019-06-13 23:05:19783 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34784 }
Blink Reformat4c46d092018-04-07 15:32:37785
786 /**
787 * @param {number} time
788 * @param {number} entryIndex
789 * @return {number}
790 */
791 function timeComparator(time, entryIndex) {
792 return time - timelineData.entryStartTimes[entryIndex];
793 }
794
795 /**
796 * @param {number} entry1
797 * @param {number} entry2
798 * @return {boolean}
799 */
800 function entriesIntersect(entry1, entry2) {
801 const start1 = timelineData.entryStartTimes[entry1];
802 const start2 = timelineData.entryStartTimes[entry2];
803 const end1 = start1 + timelineData.entryTotalTimes[entry1];
804 const end2 = start2 + timelineData.entryTotalTimes[entry2];
805 return start1 < end2 && start2 < end1;
806 }
807
808 const keys = UI.KeyboardShortcut.Keys;
809 if (e.keyCode === keys.Left.code || e.keyCode === keys.Right.code) {
810 const level = timelineData.entryLevels[this._selectedEntryIndex];
811 const levelIndexes = this._timelineLevels[level];
812 let indexOnLevel = levelIndexes.lowerBound(this._selectedEntryIndex);
813 indexOnLevel += e.keyCode === keys.Left.code ? -1 : 1;
814 e.consume(true);
Tim van der Lippe1d6e57a2019-09-30 11:55:34815 if (indexOnLevel >= 0 && indexOnLevel < levelIndexes.length) {
Tim van der Lippefd2b2ce2020-01-03 15:05:18816 this.dispatchEventToListeners(Events.EntrySelected, levelIndexes[indexOnLevel]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34817 }
Anubha Mathur72dd5822019-06-13 23:05:19818 return true;
Blink Reformat4c46d092018-04-07 15:32:37819 }
820 if (e.keyCode === keys.Up.code || e.keyCode === keys.Down.code) {
Blink Reformat4c46d092018-04-07 15:32:37821 let level = timelineData.entryLevels[this._selectedEntryIndex];
822 level += e.keyCode === keys.Up.code ? -1 : 1;
Anubha Mathur72dd5822019-06-13 23:05:19823 if (level < 0 || level >= this._timelineLevels.length) {
824 this._deselectAllEntries();
825 e.consume(true);
826 return true;
827 }
Blink Reformat4c46d092018-04-07 15:32:37828 const entryTime = timelineData.entryStartTimes[this._selectedEntryIndex] +
829 timelineData.entryTotalTimes[this._selectedEntryIndex] / 2;
830 const levelIndexes = this._timelineLevels[level];
831 let indexOnLevel = levelIndexes.upperBound(entryTime, timeComparator) - 1;
832 if (!entriesIntersect(this._selectedEntryIndex, levelIndexes[indexOnLevel])) {
833 ++indexOnLevel;
834 if (indexOnLevel >= levelIndexes.length ||
Anubha Mathur72dd5822019-06-13 23:05:19835 !entriesIntersect(this._selectedEntryIndex, levelIndexes[indexOnLevel])) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34836 if (e.code === 'ArrowDown') {
Anubha Mathur72dd5822019-06-13 23:05:19837 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34838 }
Anubha Mathur72dd5822019-06-13 23:05:19839
840 // Stay in the current group and give focus to the parent group instead of entries
841 this._deselectAllEntries();
842 e.consume(true);
843 return true;
844 }
Blink Reformat4c46d092018-04-07 15:32:37845 }
Anubha Mathur72dd5822019-06-13 23:05:19846 e.consume(true);
Tim van der Lippefd2b2ce2020-01-03 15:05:18847 this.dispatchEventToListeners(Events.EntrySelected, levelIndexes[indexOnLevel]);
Anubha Mathur72dd5822019-06-13 23:05:19848 return true;
Blink Reformat4c46d092018-04-07 15:32:37849 }
Michael Liao712bbc22019-10-15 19:21:51850 if (isEnterKey(e)) {
851 e.consume(true);
Tim van der Lippefd2b2ce2020-01-03 15:05:18852 this.dispatchEventToListeners(Events.EntryInvoked, this._selectedEntryIndex);
Michael Liao712bbc22019-10-15 19:21:51853 return true;
854 }
Anubha Mathur72dd5822019-06-13 23:05:19855 return false;
Blink Reformat4c46d092018-04-07 15:32:37856 }
857
858 /**
859 * @param {number} x
860 * @param {number} y
861 * @return {number}
862 */
863 _coordinatesToEntryIndex(x, y) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34864 if (x < 0 || y < 0) {
Blink Reformat4c46d092018-04-07 15:32:37865 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34866 }
Blink Reformat4c46d092018-04-07 15:32:37867 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:34868 if (!timelineData) {
Blink Reformat4c46d092018-04-07 15:32:37869 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34870 }
Blink Reformat4c46d092018-04-07 15:32:37871 y += this._chartViewport.scrollOffset();
872 const cursorLevel = this._visibleLevelOffsets.upperBound(y) - 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34873 if (cursorLevel < 0 || !this._visibleLevels[cursorLevel]) {
Blink Reformat4c46d092018-04-07 15:32:37874 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34875 }
Blink Reformat4c46d092018-04-07 15:32:37876 const offsetFromLevel = y - this._visibleLevelOffsets[cursorLevel];
Tim van der Lippe1d6e57a2019-09-30 11:55:34877 if (offsetFromLevel > this._levelHeight(cursorLevel)) {
Blink Reformat4c46d092018-04-07 15:32:37878 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34879 }
Alexei Filippov6c622e92018-11-10 02:13:59880
881 // Check markers first.
882 for (const [index, pos] of this._markerPositions) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34883 if (timelineData.entryLevels[index] !== cursorLevel) {
Alexei Filippov6c622e92018-11-10 02:13:59884 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34885 }
886 if (pos.x <= x && x < pos.x + pos.width) {
Alexei Filippov6c622e92018-11-10 02:13:59887 return /** @type {number} */ (index);
Tim van der Lippe1d6e57a2019-09-30 11:55:34888 }
Alexei Filippov6c622e92018-11-10 02:13:59889 }
890
891 // Check regular entries.
Blink Reformat4c46d092018-04-07 15:32:37892 const entryStartTimes = timelineData.entryStartTimes;
Alexei Filippov6c622e92018-11-10 02:13:59893 const entriesOnLevel = this._timelineLevels[cursorLevel];
Tim van der Lippe1d6e57a2019-09-30 11:55:34894 if (!entriesOnLevel || !entriesOnLevel.length) {
Blink Reformat4c46d092018-04-07 15:32:37895 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34896 }
Blink Reformat4c46d092018-04-07 15:32:37897
Blink Reformat4c46d092018-04-07 15:32:37898 const cursorTime = this._chartViewport.pixelToTime(x);
Alexei Filippov6c622e92018-11-10 02:13:59899 const indexOnLevel = Math.max(
900 entriesOnLevel.upperBound(cursorTime, (time, entryIndex) => time - entryStartTimes[entryIndex]) - 1, 0);
Blink Reformat4c46d092018-04-07 15:32:37901
902 /**
Tim van der Lippefd2b2ce2020-01-03 15:05:18903 * @this {FlameChart}
Alexei Filippov72d792d2018-11-06 07:15:04904 * @param {number|undefined} entryIndex
Blink Reformat4c46d092018-04-07 15:32:37905 * @return {boolean}
906 */
907 function checkEntryHit(entryIndex) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34908 if (entryIndex === undefined) {
Blink Reformat4c46d092018-04-07 15:32:37909 return false;
Tim van der Lippe1d6e57a2019-09-30 11:55:34910 }
Blink Reformat4c46d092018-04-07 15:32:37911 const startTime = entryStartTimes[entryIndex];
Alexei Filippov6c622e92018-11-10 02:13:59912 const duration = timelineData.entryTotalTimes[entryIndex];
Blink Reformat4c46d092018-04-07 15:32:37913 const startX = this._chartViewport.timeToPosition(startTime);
Alexei Filippov6c622e92018-11-10 02:13:59914 const endX = this._chartViewport.timeToPosition(startTime + duration);
915 const barThresholdPx = 3;
Blink Reformat4c46d092018-04-07 15:32:37916 return startX - barThresholdPx < x && x < endX + barThresholdPx;
917 }
918
Alexei Filippov6c622e92018-11-10 02:13:59919 let entryIndex = entriesOnLevel[indexOnLevel];
Tim van der Lippe1d6e57a2019-09-30 11:55:34920 if (checkEntryHit.call(this, entryIndex)) {
Blink Reformat4c46d092018-04-07 15:32:37921 return entryIndex;
Tim van der Lippe1d6e57a2019-09-30 11:55:34922 }
Alexei Filippov6c622e92018-11-10 02:13:59923 entryIndex = entriesOnLevel[indexOnLevel + 1];
Tim van der Lippe1d6e57a2019-09-30 11:55:34924 if (checkEntryHit.call(this, entryIndex)) {
Blink Reformat4c46d092018-04-07 15:32:37925 return entryIndex;
Tim van der Lippe1d6e57a2019-09-30 11:55:34926 }
Blink Reformat4c46d092018-04-07 15:32:37927 return -1;
928 }
929
930 /**
931 * @param {number} x
932 * @param {number} y
933 * @param {boolean} headerOnly
934 * @return {number}
935 */
936 _coordinatesToGroupIndex(x, y, headerOnly) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34937 if (x < 0 || y < 0) {
Blink Reformat4c46d092018-04-07 15:32:37938 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34939 }
Blink Reformat4c46d092018-04-07 15:32:37940 y += this._chartViewport.scrollOffset();
941 const groups = this._rawTimelineData.groups || [];
942 const group = this._groupOffsets.upperBound(y) - 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34943 if (group < 0 || group >= groups.length) {
Blink Reformat4c46d092018-04-07 15:32:37944 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34945 }
Blink Reformat4c46d092018-04-07 15:32:37946 const height = headerOnly ? groups[group].style.height : this._groupOffsets[group + 1] - this._groupOffsets[group];
Tim van der Lippe1d6e57a2019-09-30 11:55:34947 if (y - this._groupOffsets[group] >= height) {
Blink Reformat4c46d092018-04-07 15:32:37948 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34949 }
950 if (!headerOnly) {
Blink Reformat4c46d092018-04-07 15:32:37951 return group;
Tim van der Lippe1d6e57a2019-09-30 11:55:34952 }
Blink Reformat4c46d092018-04-07 15:32:37953
954 const context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
955 context.save();
956 context.font = groups[group].style.font;
957 const right = this._headerLeftPadding + this._labelWidthForGroup(context, groups[group]);
958 context.restore();
Tim van der Lippe1d6e57a2019-09-30 11:55:34959 if (x > right) {
Blink Reformat4c46d092018-04-07 15:32:37960 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34961 }
Blink Reformat4c46d092018-04-07 15:32:37962
963 return group;
964 }
965
966 /**
967 * @param {number} x
968 * @return {number}
969 */
970 _markerIndexAtPosition(x) {
971 const markers = this._timelineData().markers;
Tim van der Lippe1d6e57a2019-09-30 11:55:34972 if (!markers) {
Blink Reformat4c46d092018-04-07 15:32:37973 return -1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34974 }
Blink Reformat4c46d092018-04-07 15:32:37975 const /** @const */ accurracyOffsetPx = 4;
976 const time = this._chartViewport.pixelToTime(x);
977 const leftTime = this._chartViewport.pixelToTime(x - accurracyOffsetPx);
978 const rightTime = this._chartViewport.pixelToTime(x + accurracyOffsetPx);
979 const left = this._markerIndexBeforeTime(leftTime);
980 let markerIndex = -1;
981 let distance = Infinity;
982 for (let i = left; i < markers.length && markers[i].startTime() < rightTime; i++) {
983 const nextDistance = Math.abs(markers[i].startTime() - time);
984 if (nextDistance < distance) {
985 markerIndex = i;
986 distance = nextDistance;
987 }
988 }
989 return markerIndex;
990 }
991
992 /**
993 * @param {number} time
994 * @return {number}
995 */
996 _markerIndexBeforeTime(time) {
997 return this._timelineData().markers.lowerBound(
998 time, (markerTimestamp, marker) => markerTimestamp - marker.startTime());
999 }
1000
1001 _draw() {
1002 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:341003 if (!timelineData) {
Blink Reformat4c46d092018-04-07 15:32:371004 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341005 }
Blink Reformat4c46d092018-04-07 15:32:371006
1007 const width = this._offsetWidth;
1008 const height = this._offsetHeight;
1009 const context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
1010 context.save();
1011 const ratio = window.devicePixelRatio;
1012 const top = this._chartViewport.scrollOffset();
1013 context.scale(ratio, ratio);
Alexei Filippov57ccafb2018-08-14 20:59:051014 context.fillStyle = 'rgba(0, 0, 0, 0)';
1015 context.fillRect(0, 0, width, height);
Blink Reformat4c46d092018-04-07 15:32:371016 context.translate(0, -top);
Tim van der Lippebb769172020-02-12 15:32:441017 const defaultFont = '11px ' + Host.Platform.fontFamily();
Blink Reformat4c46d092018-04-07 15:32:371018 context.font = defaultFont;
1019
Paul Lewis49b16822020-02-21 14:22:031020 const candyStripePattern = context.createPattern(this._candyStripeCanvas, 'repeat');
1021
Blink Reformat4c46d092018-04-07 15:32:371022 const entryTotalTimes = timelineData.entryTotalTimes;
1023 const entryStartTimes = timelineData.entryStartTimes;
1024 const entryLevels = timelineData.entryLevels;
1025 const timeToPixel = this._chartViewport.timeToPixel();
1026
1027 const titleIndices = [];
1028 const markerIndices = [];
1029 const textPadding = this._textPadding;
Mathias Bynens23ee1aa2020-03-02 12:06:381030 const minTextWidth = 2 * textPadding + UI.UIUtils.measureTextWidth(context, '…');
Alexei Filippov57ccafb2018-08-14 20:59:051031 const minTextWidthDuration = this._chartViewport.pixelToTimeOffset(minTextWidth);
Blink Reformat4c46d092018-04-07 15:32:371032 const minVisibleBarLevel = Math.max(this._visibleLevelOffsets.upperBound(top) - 1, 0);
Alexei Filippov6c622e92018-11-10 02:13:591033 this._markerPositions.clear();
Blink Reformat4c46d092018-04-07 15:32:371034
Paul Lewis49b16822020-02-21 14:22:031035 let mainThreadTopLevel = -1;
1036
1037 // Find the main thread so that we can mark tasks longer than 50ms.
1038 if ('groups' in timelineData && Array.isArray(timelineData.groups)) {
1039 const mainThread = timelineData.groups.find(v => {
1040 if (!v._track) {
1041 return false;
1042 }
1043
1044 return v._track.name === 'CrRendererMain';
1045 });
1046
1047 if (mainThread) {
1048 mainThreadTopLevel = mainThread.startLevel;
1049 }
1050 }
1051
Paul Lewis06985d42020-04-30 14:51:161052 /** @type {!Map<string, {indexes: !Array<number>}>} */
Blink Reformat4c46d092018-04-07 15:32:371053 const colorBuckets = new Map();
1054 for (let level = minVisibleBarLevel; level < this._dataProvider.maxStackDepth(); ++level) {
Tim van der Lippe1d6e57a2019-09-30 11:55:341055 if (this._levelToOffset(level) > top + height) {
Blink Reformat4c46d092018-04-07 15:32:371056 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:341057 }
1058 if (!this._visibleLevels[level]) {
Blink Reformat4c46d092018-04-07 15:32:371059 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341060 }
Blink Reformat4c46d092018-04-07 15:32:371061
1062 // Entries are ordered by start time within a level, so find the last visible entry.
1063 const levelIndexes = this._timelineLevels[level];
1064 const rightIndexOnLevel =
1065 levelIndexes.lowerBound(
1066 this._chartViewport.windowRightTime(), (time, entryIndex) => time - entryStartTimes[entryIndex]) -
1067 1;
1068 let lastDrawOffset = Infinity;
1069 for (let entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
1070 const entryIndex = levelIndexes[entryIndexOnLevel];
Alexei Filippov72d792d2018-11-06 07:15:041071 const duration = entryTotalTimes[entryIndex];
1072 if (isNaN(duration)) {
Alexei Filippov57ccafb2018-08-14 20:59:051073 markerIndices.push(entryIndex);
Alexei Filippov72d792d2018-11-06 07:15:041074 continue;
1075 }
Tim van der Lippe1d6e57a2019-09-30 11:55:341076 if (duration >= minTextWidthDuration || this._forceDecorationCache[entryIndex]) {
Alexei Filippov57ccafb2018-08-14 20:59:051077 titleIndices.push(entryIndex);
Tim van der Lippe1d6e57a2019-09-30 11:55:341078 }
Alexei Filippov57ccafb2018-08-14 20:59:051079
Blink Reformat4c46d092018-04-07 15:32:371080 const entryStartTime = entryStartTimes[entryIndex];
Alexei Filippov57ccafb2018-08-14 20:59:051081 const entryOffsetRight = entryStartTime + duration;
Tim van der Lippe1d6e57a2019-09-30 11:55:341082 if (entryOffsetRight <= this._chartViewport.windowLeftTime()) {
Blink Reformat4c46d092018-04-07 15:32:371083 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:341084 }
1085 if (this._useWebGL) {
Alexei Filippov57ccafb2018-08-14 20:59:051086 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341087 }
Blink Reformat4c46d092018-04-07 15:32:371088
1089 const barX = this._timeToPositionClipped(entryStartTime);
1090 // 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:341091 if (barX >= lastDrawOffset) {
Blink Reformat4c46d092018-04-07 15:32:371092 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341093 }
Blink Reformat4c46d092018-04-07 15:32:371094 lastDrawOffset = barX;
1095
Alexei Filippove5197622018-08-18 03:04:331096 const color = this._entryColorsCache[entryIndex];
Blink Reformat4c46d092018-04-07 15:32:371097 let bucket = colorBuckets.get(color);
1098 if (!bucket) {
Paul Lewis06985d42020-04-30 14:51:161099 bucket = {indexes: []};
Blink Reformat4c46d092018-04-07 15:32:371100 colorBuckets.set(color, bucket);
1101 }
Paul Lewis49b16822020-02-21 14:22:031102 bucket.indexes.push(entryIndex);
Blink Reformat4c46d092018-04-07 15:32:371103 }
1104 }
1105
Alexei Filippov57ccafb2018-08-14 20:59:051106 if (this._useWebGL) {
1107 this._drawGL();
1108 } else {
1109 context.save();
Alexei Filippov5f6b11d2018-08-18 03:30:281110 this._forEachGroupInViewport((offset, index, group, isFirst, groupHeight) => {
Anubha Mathur72dd5822019-06-13 23:05:191111 if (this._isGroupFocused(index)) {
Alexei Filippov57ccafb2018-08-14 20:59:051112 context.fillStyle = this._selectedGroupBackroundColor;
1113 context.fillRect(0, offset, width, groupHeight - group.style.padding);
Blink Reformat4c46d092018-04-07 15:32:371114 }
Alexei Filippov57ccafb2018-08-14 20:59:051115 });
1116 context.restore();
1117
Paul Lewis06985d42020-04-30 14:51:161118 for (const [color, {indexes}] of colorBuckets) {
Alexei Filippov57ccafb2018-08-14 20:59:051119 context.beginPath();
1120 for (let i = 0; i < indexes.length; ++i) {
1121 const entryIndex = indexes[i];
Alexei Filippov72d792d2018-11-06 07:15:041122 const duration = entryTotalTimes[entryIndex];
Tim van der Lippe1d6e57a2019-09-30 11:55:341123 if (isNaN(duration)) {
Alexei Filippov72d792d2018-11-06 07:15:041124 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341125 }
Alexei Filippov57ccafb2018-08-14 20:59:051126 const entryStartTime = entryStartTimes[entryIndex];
1127 const barX = this._timeToPositionClipped(entryStartTime);
Alexei Filippov57ccafb2018-08-14 20:59:051128 const barLevel = entryLevels[entryIndex];
1129 const barHeight = this._levelHeight(barLevel);
1130 const barY = this._levelToOffset(barLevel);
Alexei Filippov57ccafb2018-08-14 20:59:051131 const barRight = this._timeToPositionClipped(entryStartTime + duration);
1132 const barWidth = Math.max(barRight - barX, 1);
Alexei Filippov72d792d2018-11-06 07:15:041133 context.rect(barX, barY, barWidth - 0.4, barHeight - 1);
Alexei Filippov57ccafb2018-08-14 20:59:051134 }
Alexei Filippov57ccafb2018-08-14 20:59:051135 context.fillStyle = color;
1136 context.fill();
Paul Lewis49b16822020-02-21 14:22:031137
1138 // Draw long task regions.
1139 context.beginPath();
1140 for (let i = 0; i < indexes.length; ++i) {
1141 const entryIndex = indexes[i];
1142 const duration = entryTotalTimes[entryIndex];
Paul Lewis06985d42020-04-30 14:51:161143 const showLongDurations = entryLevels[entryIndex] === mainThreadTopLevel;
Paul Lewis49b16822020-02-21 14:22:031144
1145 if (!showLongDurations) {
1146 continue;
1147 }
1148
Paul Lewis06985d42020-04-30 14:51:161149 if (isNaN(duration) || duration < 50) {
Paul Lewis49b16822020-02-21 14:22:031150 continue;
1151 }
1152
1153 const entryStartTime = entryStartTimes[entryIndex];
1154 const barX = this._timeToPositionClipped(entryStartTime + 50);
1155 const barLevel = entryLevels[entryIndex];
1156 const barHeight = this._levelHeight(barLevel);
1157 const barY = this._levelToOffset(barLevel);
1158 const barRight = this._timeToPositionClipped(entryStartTime + duration);
1159 const barWidth = Math.max(barRight - barX, 1);
1160 context.rect(barX, barY, barWidth - 0.4, barHeight - 1);
1161 }
1162
1163 context.fillStyle = candyStripePattern;
1164 context.fill();
Blink Reformat4c46d092018-04-07 15:32:371165 }
Blink Reformat4c46d092018-04-07 15:32:371166 }
1167
Alexei Filippov72d792d2018-11-06 07:15:041168 context.textBaseline = 'alphabetic';
Blink Reformat4c46d092018-04-07 15:32:371169 context.beginPath();
Alexei Filippov6c622e92018-11-10 02:13:591170 let lastMarkerLevel = -1;
1171 let lastMarkerX = -Infinity;
1172 // Markers are sorted top to bottom, right to left.
Alexei Filippov72d792d2018-11-06 07:15:041173 for (let m = markerIndices.length - 1; m >= 0; --m) {
Blink Reformat4c46d092018-04-07 15:32:371174 const entryIndex = markerIndices[m];
Alexei Filippov72d792d2018-11-06 07:15:041175 const title = this._dataProvider.entryTitle(entryIndex);
Tim van der Lippe1d6e57a2019-09-30 11:55:341176 if (!title) {
Alexei Filippov72d792d2018-11-06 07:15:041177 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341178 }
Alexei Filippov6c622e92018-11-10 02:13:591179 const entryStartTime = entryStartTimes[entryIndex];
1180 const level = entryLevels[entryIndex];
Tim van der Lippe1d6e57a2019-09-30 11:55:341181 if (lastMarkerLevel !== level) {
Alexei Filippov6c622e92018-11-10 02:13:591182 lastMarkerX = -Infinity;
Tim van der Lippe1d6e57a2019-09-30 11:55:341183 }
Alexei Filippov6c622e92018-11-10 02:13:591184 const x = Math.max(this._chartViewport.timeToPosition(entryStartTime), lastMarkerX);
1185 const y = this._levelToOffset(level);
1186 const h = this._levelHeight(level);
1187 const padding = 4;
Tim van der Lippebb769172020-02-12 15:32:441188 const width = Math.ceil(UI.UIUtils.measureTextWidth(context, title)) + 2 * padding;
Alexei Filippov214df612018-11-13 19:14:001189 lastMarkerX = x + width + 1;
Alexei Filippov6c622e92018-11-10 02:13:591190 lastMarkerLevel = level;
1191 this._markerPositions.set(entryIndex, {x, width});
Alexei Filippov72d792d2018-11-06 07:15:041192 context.fillStyle = this._dataProvider.entryColor(entryIndex);
1193 context.fillRect(x, y, width, h - 1);
1194 context.fillStyle = 'white';
Alexei Filippov72d792d2018-11-06 07:15:041195 context.fillText(title, x + padding, y + h - this._textBaseline);
Blink Reformat4c46d092018-04-07 15:32:371196 }
1197 context.strokeStyle = 'rgba(0, 0, 0, 0.2)';
1198 context.stroke();
1199
Blink Reformat4c46d092018-04-07 15:32:371200 for (let i = 0; i < titleIndices.length; ++i) {
1201 const entryIndex = titleIndices[i];
1202 const entryStartTime = entryStartTimes[entryIndex];
1203 const barX = this._timeToPositionClipped(entryStartTime);
1204 const barRight = Math.min(this._timeToPositionClipped(entryStartTime + entryTotalTimes[entryIndex]), width) + 1;
1205 const barWidth = barRight - barX;
1206 const barLevel = entryLevels[entryIndex];
1207 const barY = this._levelToOffset(barLevel);
1208 let text = this._dataProvider.entryTitle(entryIndex);
1209 if (text && text.length) {
1210 context.font = this._dataProvider.entryFont(entryIndex) || defaultFont;
Tim van der Lippebb769172020-02-12 15:32:441211 text = UI.UIUtils.trimTextMiddle(context, text, barWidth - 2 * textPadding);
Blink Reformat4c46d092018-04-07 15:32:371212 }
1213 const unclippedBarX = this._chartViewport.timeToPosition(entryStartTime);
1214 const barHeight = this._levelHeight(barLevel);
1215 if (this._dataProvider.decorateEntry(
Tim van der Lippe1d6e57a2019-09-30 11:55:341216 entryIndex, context, text, barX, barY, barWidth, barHeight, unclippedBarX, timeToPixel)) {
Blink Reformat4c46d092018-04-07 15:32:371217 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341218 }
1219 if (!text || !text.length) {
Blink Reformat4c46d092018-04-07 15:32:371220 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341221 }
Blink Reformat4c46d092018-04-07 15:32:371222 context.fillStyle = this._dataProvider.textColor(entryIndex);
1223 context.fillText(text, barX + textPadding, barY + barHeight - this._textBaseline);
1224 }
1225
1226 context.restore();
1227
1228 this._drawGroupHeaders(width, height);
1229 this._drawFlowEvents(context, width, height);
1230 this._drawMarkers();
Paul Lewis2cfa94e2020-01-13 10:30:151231 const dividersData = TimelineGrid.calculateGridOffsets(this);
Paul Lewisead45752020-06-23 09:51:361232 const navStartTimes = Array.from(this._dataProvider.navStartTimes().values());
1233
1234 let navStartTimeIndex = 0;
1235 const drawAdjustedTime = time => {
1236 if (navStartTimes.length === 0) {
1237 return this.formatValue(time, dividersData.precision);
1238 }
1239
1240 // Track when the time crosses the boundary to the next nav start record,
1241 // and when it does, move the nav start array index accordingly.
1242 const hasNextNavStartTime = navStartTimes.length > navStartTimeIndex + 1;
1243 if (hasNextNavStartTime && time > navStartTimes[navStartTimeIndex + 1].startTime) {
1244 navStartTimeIndex++;
1245 }
1246
1247 // Adjust the time by the nearest nav start marker's value.
1248 const nearestMarker = navStartTimes[navStartTimeIndex];
1249 if (nearestMarker) {
1250 time -= nearestMarker.startTime - this.zeroTime();
1251 }
1252
1253 return this.formatValue(time, dividersData.precision);
1254 };
1255
Paul Lewis2cfa94e2020-01-13 10:30:151256 TimelineGrid.drawCanvasGrid(context, dividersData);
Blink Reformat4c46d092018-04-07 15:32:371257 if (this._rulerEnabled) {
Paul Lewisead45752020-06-23 09:51:361258 TimelineGrid.drawCanvasHeaders(context, dividersData, drawAdjustedTime, 3, HeaderHeight);
Blink Reformat4c46d092018-04-07 15:32:371259 }
1260
1261 this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
1262 this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
1263 this._updateMarkerHighlight();
1264 }
1265
Alexei Filippov57ccafb2018-08-14 20:59:051266 _initWebGL() {
1267 const gl = /** @type {?WebGLRenderingContext} */ (this._canvasGL.getContext('webgl'));
1268 if (!gl) {
1269 console.error('Failed to obtain WebGL context.');
1270 this._useWebGL = false; // Fallback to use canvas.
1271 return;
1272 }
1273
1274 const vertexShaderSource = `
1275 attribute vec2 aVertexPosition;
Alexei Filippovaf3ffed2018-08-18 01:56:091276 attribute float aVertexColor;
Alexei Filippov57ccafb2018-08-14 20:59:051277
1278 uniform vec2 uScalingFactor;
1279 uniform vec2 uShiftVector;
1280
Alexei Filippovaf3ffed2018-08-18 01:56:091281 varying mediump vec2 vPalettePosition;
Alexei Filippov57ccafb2018-08-14 20:59:051282
1283 void main() {
1284 vec2 shiftedPosition = aVertexPosition - uShiftVector;
1285 gl_Position = vec4(shiftedPosition * uScalingFactor + vec2(-1.0, 1.0), 0.0, 1.0);
Alexei Filippovaf3ffed2018-08-18 01:56:091286 vPalettePosition = vec2(aVertexColor, 0.5);
Alexei Filippov57ccafb2018-08-14 20:59:051287 }`;
1288
1289 const fragmentShaderSource = `
Alexei Filippovaf3ffed2018-08-18 01:56:091290 varying mediump vec2 vPalettePosition;
1291 uniform sampler2D uSampler;
Alexei Filippov57ccafb2018-08-14 20:59:051292
1293 void main() {
Alexei Filippovaf3ffed2018-08-18 01:56:091294 gl_FragColor = texture2D(uSampler, vPalettePosition);
Alexei Filippov57ccafb2018-08-14 20:59:051295 }`;
1296
1297 /**
1298 * @param {!WebGLRenderingContext} gl
1299 * @param {number} type
1300 * @param {string} source
1301 * @return {?WebGLShader}
1302 */
1303 function loadShader(gl, type, source) {
1304 const shader = gl.createShader(type);
1305 gl.shaderSource(shader, source);
1306 gl.compileShader(shader);
Tim van der Lippe1d6e57a2019-09-30 11:55:341307 if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
Alexei Filippov57ccafb2018-08-14 20:59:051308 return shader;
Tim van der Lippe1d6e57a2019-09-30 11:55:341309 }
Alexei Filippov57ccafb2018-08-14 20:59:051310 console.error('Shader compile error: ' + gl.getShaderInfoLog(shader));
1311 gl.deleteShader(shader);
1312 return null;
1313 }
1314
1315 const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
1316 const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
1317
1318 const shaderProgram = gl.createProgram();
1319 gl.attachShader(shaderProgram, vertexShader);
1320 gl.attachShader(shaderProgram, fragmentShader);
1321 gl.linkProgram(shaderProgram);
1322
1323 if (gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
1324 this._shaderProgram = shaderProgram;
1325 gl.useProgram(shaderProgram);
1326 } else {
1327 console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
1328 this._shaderProgram = null;
1329 }
Alexei Filippovaf3ffed2018-08-18 01:56:091330
1331 this._vertexBuffer = gl.createBuffer();
1332 this._colorBuffer = gl.createBuffer();
1333
1334 this._uScalingFactor = gl.getUniformLocation(shaderProgram, 'uScalingFactor');
1335 this._uShiftVector = gl.getUniformLocation(shaderProgram, 'uShiftVector');
1336 const uSampler = gl.getUniformLocation(shaderProgram, 'uSampler');
1337 gl.uniform1i(uSampler, 0);
1338 this._aVertexPosition = gl.getAttribLocation(this._shaderProgram, 'aVertexPosition');
1339 this._aVertexColor = gl.getAttribLocation(this._shaderProgram, 'aVertexColor');
1340 gl.enableVertexAttribArray(this._aVertexPosition);
1341 gl.enableVertexAttribArray(this._aVertexColor);
Alexei Filippov57ccafb2018-08-14 20:59:051342 }
1343
1344 _setupGLGeometry() {
1345 const gl = /** @type {?WebGLRenderingContext} */ (this._canvasGL.getContext('webgl'));
Tim van der Lippe1d6e57a2019-09-30 11:55:341346 if (!gl) {
Alexei Filippov57ccafb2018-08-14 20:59:051347 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341348 }
Alexei Filippov57ccafb2018-08-14 20:59:051349
1350 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:341351 if (!timelineData) {
Alexei Filippov57ccafb2018-08-14 20:59:051352 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341353 }
Alexei Filippov57ccafb2018-08-14 20:59:051354
1355 const entryTotalTimes = timelineData.entryTotalTimes;
1356 const entryStartTimes = timelineData.entryStartTimes;
1357 const entryLevels = timelineData.entryLevels;
1358
Alexei Filippovaf3ffed2018-08-18 01:56:091359 const verticesPerBar = 6;
1360 const vertexArray = new Float32Array(entryTotalTimes.length * verticesPerBar * 2);
1361 let colorArray = new Uint8Array(entryTotalTimes.length * verticesPerBar);
Alexei Filippov57ccafb2018-08-14 20:59:051362 let vertex = 0;
Alexei Filippovaf3ffed2018-08-18 01:56:091363 /** @type {!Map<string, number>} */
Alexei Filippovc34372c2018-08-16 20:37:391364 const parsedColorCache = new Map();
Alexei Filippovaf3ffed2018-08-18 01:56:091365 /** @type {!Array<number>} */
1366 const colors = [];
1367
Alexei Filippov5f6b11d2018-08-18 03:30:281368 const collapsedOverviewLevels = new Array(this._visibleLevels.length);
1369 const groups = this._rawTimelineData.groups || [];
1370 this._forEachGroup((offset, index, group) => {
Tim van der Lippe1d6e57a2019-09-30 11:55:341371 if (group.style.useFirstLineForOverview || !this._isGroupCollapsible(index) || group.expanded) {
Alexei Filippov5f6b11d2018-08-18 03:30:281372 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341373 }
Alexei Filippov5f6b11d2018-08-18 03:30:281374 let nextGroup = index + 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:341375 while (nextGroup < groups.length && groups[nextGroup].style.nestingLevel > group.style.nestingLevel) {
Alexei Filippov5f6b11d2018-08-18 03:30:281376 ++nextGroup;
Tim van der Lippe1d6e57a2019-09-30 11:55:341377 }
Alexei Filippov5f6b11d2018-08-18 03:30:281378 const endLevel = nextGroup < groups.length ? groups[nextGroup].startLevel : this._dataProvider.maxStackDepth();
Tim van der Lippe1d6e57a2019-09-30 11:55:341379 for (let i = group.startLevel; i < endLevel; ++i) {
Alexei Filippov5f6b11d2018-08-18 03:30:281380 collapsedOverviewLevels[i] = offset;
Tim van der Lippe1d6e57a2019-09-30 11:55:341381 }
Alexei Filippov5f6b11d2018-08-18 03:30:281382 });
1383
Alexei Filippov57ccafb2018-08-14 20:59:051384 for (let i = 0; i < entryTotalTimes.length; ++i) {
1385 const level = entryLevels[i];
Alexei Filippov5f6b11d2018-08-18 03:30:281386 const collapsedGroupOffset = collapsedOverviewLevels[level];
Tim van der Lippe1d6e57a2019-09-30 11:55:341387 if (!this._visibleLevels[level] && !collapsedGroupOffset) {
Alexei Filippov57ccafb2018-08-14 20:59:051388 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341389 }
Alexei Filippove5197622018-08-18 03:04:331390 const color = this._entryColorsCache[i];
Tim van der Lippe1d6e57a2019-09-30 11:55:341391 if (!color) {
Alexei Filippov57ccafb2018-08-14 20:59:051392 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341393 }
Alexei Filippovaf3ffed2018-08-18 01:56:091394 let colorIndex = parsedColorCache.get(color);
1395 if (colorIndex === undefined) {
Tim van der Lippebb769172020-02-12 15:32:441396 const rgba = Common.Color.Color.parse(color).canonicalRGBA();
Alexei Filippovc34372c2018-08-16 20:37:391397 rgba[3] = Math.round(rgba[3] * 255);
Alexei Filippovaf3ffed2018-08-18 01:56:091398 colorIndex = colors.length / 4;
1399 colors.push(...rgba);
Tim van der Lippe1d6e57a2019-09-30 11:55:341400 if (colorIndex === 256) {
Alexei Filippovaf3ffed2018-08-18 01:56:091401 colorArray = new Uint16Array(colorArray);
Tim van der Lippe1d6e57a2019-09-30 11:55:341402 }
Alexei Filippovaf3ffed2018-08-18 01:56:091403 parsedColorCache.set(color, colorIndex);
Alexei Filippovc34372c2018-08-16 20:37:391404 }
Tim van der Lippe1d6e57a2019-09-30 11:55:341405 for (let j = 0; j < verticesPerBar; ++j) {
Alexei Filippovaf3ffed2018-08-18 01:56:091406 colorArray[vertex + j] = colorIndex;
Tim van der Lippe1d6e57a2019-09-30 11:55:341407 }
Alexei Filippov57ccafb2018-08-14 20:59:051408
1409 const vpos = vertex * 2;
1410 const x0 = entryStartTimes[i] - this._minimumBoundary;
1411 const x1 = x0 + entryTotalTimes[i];
Alexei Filippov5f6b11d2018-08-18 03:30:281412 const y0 = collapsedGroupOffset || this._levelToOffset(level);
Alexei Filippov57ccafb2018-08-14 20:59:051413 const y1 = y0 + this._levelHeight(level) - 1;
1414 vertexArray[vpos + 0] = x0;
1415 vertexArray[vpos + 1] = y0;
1416 vertexArray[vpos + 2] = x1;
1417 vertexArray[vpos + 3] = y0;
1418 vertexArray[vpos + 4] = x0;
1419 vertexArray[vpos + 5] = y1;
1420 vertexArray[vpos + 6] = x0;
1421 vertexArray[vpos + 7] = y1;
1422 vertexArray[vpos + 8] = x1;
1423 vertexArray[vpos + 9] = y0;
1424 vertexArray[vpos + 10] = x1;
1425 vertexArray[vpos + 11] = y1;
1426
Alexei Filippovaf3ffed2018-08-18 01:56:091427 vertex += verticesPerBar;
Alexei Filippov57ccafb2018-08-14 20:59:051428 }
Alexei Filippov57ccafb2018-08-14 20:59:051429 this._vertexCount = vertex;
1430
Alexei Filippovaf3ffed2018-08-18 01:56:091431 const paletteTexture = gl.createTexture();
1432 gl.bindTexture(gl.TEXTURE_2D, paletteTexture);
1433 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
1434 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
1435 gl.activeTexture(gl.TEXTURE0);
1436
1437 const numColors = colors.length / 4;
1438 const useShortForColors = numColors >= 256;
1439 const width = !useShortForColors ? 256 : Math.min(1 << 16, gl.getParameter(gl.MAX_TEXTURE_SIZE));
1440 console.assert(numColors <= width, 'Too many colors');
1441 const height = 1;
1442 const colorIndexType = useShortForColors ? gl.UNSIGNED_SHORT : gl.UNSIGNED_BYTE;
1443 if (useShortForColors) {
1444 const factor = (1 << 16) / width;
Tim van der Lippe1d6e57a2019-09-30 11:55:341445 for (let i = 0; i < vertex; ++i) {
Alexei Filippovaf3ffed2018-08-18 01:56:091446 colorArray[i] *= factor;
Tim van der Lippe1d6e57a2019-09-30 11:55:341447 }
Alexei Filippovaf3ffed2018-08-18 01:56:091448 }
1449
1450 const pixels = new Uint8Array(width * 4);
1451 pixels.set(colors);
1452 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
1453
1454 gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
Alexei Filippov57ccafb2018-08-14 20:59:051455 gl.bufferData(gl.ARRAY_BUFFER, vertexArray, gl.STATIC_DRAW);
Alexei Filippovaf3ffed2018-08-18 01:56:091456 gl.vertexAttribPointer(this._aVertexPosition, /* vertexComponents */ 2, gl.FLOAT, false, 0, 0);
Alexei Filippov57ccafb2018-08-14 20:59:051457
Alexei Filippovaf3ffed2018-08-18 01:56:091458 gl.bindBuffer(gl.ARRAY_BUFFER, this._colorBuffer);
Alexei Filippov57ccafb2018-08-14 20:59:051459 gl.bufferData(gl.ARRAY_BUFFER, colorArray, gl.STATIC_DRAW);
Alexei Filippovaf3ffed2018-08-18 01:56:091460 gl.vertexAttribPointer(this._aVertexColor, /* colorComponents */ 1, colorIndexType, true, 0, 0);
Alexei Filippov57ccafb2018-08-14 20:59:051461 }
1462
1463 _drawGL() {
1464 const gl = /** @type {?WebGLRenderingContext} */ (this._canvasGL.getContext('webgl'));
Tim van der Lippe1d6e57a2019-09-30 11:55:341465 if (!gl) {
Alexei Filippov57ccafb2018-08-14 20:59:051466 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341467 }
Alexei Filippov57ccafb2018-08-14 20:59:051468 const timelineData = this._timelineData();
Tim van der Lippe1d6e57a2019-09-30 11:55:341469 if (!timelineData) {
Alexei Filippov57ccafb2018-08-14 20:59:051470 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341471 }
Alexei Filippov57ccafb2018-08-14 20:59:051472
Alexei Filippov57ccafb2018-08-14 20:59:051473 if (!this._prevTimelineData || timelineData.entryTotalTimes !== this._prevTimelineData.entryTotalTimes) {
1474 this._prevTimelineData = timelineData;
1475 this._setupGLGeometry();
1476 }
1477
Alexei Filippovc34372c2018-08-16 20:37:391478 gl.viewport(0, 0, this._canvasGL.width, this._canvasGL.height);
Alexei Filippov57ccafb2018-08-14 20:59:051479
Tim van der Lippe1d6e57a2019-09-30 11:55:341480 if (!this._vertexCount) {
Alexei Filippov57ccafb2018-08-14 20:59:051481 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341482 }
Alexei Filippov57ccafb2018-08-14 20:59:051483
Alexei Filippovc34372c2018-08-16 20:37:391484 const viewportScale = [2.0 / this.boundarySpan(), -2.0 * window.devicePixelRatio / this._canvasGL.height];
1485 const viewportShift = [this.minimumBoundary() - this.zeroTime(), this._chartViewport.scrollOffset()];
1486 gl.uniform2fv(this._uScalingFactor, viewportScale);
1487 gl.uniform2fv(this._uShiftVector, viewportShift);
Alexei Filippov57ccafb2018-08-14 20:59:051488
1489 gl.drawArrays(gl.TRIANGLES, 0, this._vertexCount);
1490 }
1491
Blink Reformat4c46d092018-04-07 15:32:371492 /**
1493 * @param {number} width
1494 * @param {number} height
1495 */
1496 _drawGroupHeaders(width, height) {
1497 const context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
1498 const top = this._chartViewport.scrollOffset();
1499 const ratio = window.devicePixelRatio;
1500 const groups = this._rawTimelineData.groups || [];
Tim van der Lippe1d6e57a2019-09-30 11:55:341501 if (!groups.length) {
Blink Reformat4c46d092018-04-07 15:32:371502 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341503 }
Blink Reformat4c46d092018-04-07 15:32:371504
1505 const groupOffsets = this._groupOffsets;
1506 const lastGroupOffset = Array.prototype.peekLast.call(groupOffsets);
Paul Lewisca569a52020-09-09 16:11:511507 const colorUsage = ThemeSupport.ThemeSupport.ColorUsage;
Blink Reformat4c46d092018-04-07 15:32:371508
1509 context.save();
1510 context.scale(ratio, ratio);
1511 context.translate(0, -top);
Tim van der Lippebb769172020-02-12 15:32:441512 const defaultFont = '11px ' + Host.Platform.fontFamily();
Blink Reformat4c46d092018-04-07 15:32:371513 context.font = defaultFont;
1514
Paul Lewisca569a52020-09-09 16:11:511515 context.fillStyle = ThemeSupport.ThemeSupport.instance().patchColorText('#fff', colorUsage.Background);
Alexei Filippov5f6b11d2018-08-18 03:30:281516 this._forEachGroupInViewport((offset, index, group) => {
Blink Reformat4c46d092018-04-07 15:32:371517 const paddingHeight = group.style.padding;
Tim van der Lippe1d6e57a2019-09-30 11:55:341518 if (paddingHeight < 5) {
Blink Reformat4c46d092018-04-07 15:32:371519 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341520 }
Blink Reformat4c46d092018-04-07 15:32:371521 context.fillRect(0, offset - paddingHeight + 2, width, paddingHeight - 4);
1522 });
Tim van der Lippe1d6e57a2019-09-30 11:55:341523 if (groups.length && lastGroupOffset < top + height) {
Blink Reformat4c46d092018-04-07 15:32:371524 context.fillRect(0, lastGroupOffset + 2, width, top + height - lastGroupOffset);
Tim van der Lippe1d6e57a2019-09-30 11:55:341525 }
Blink Reformat4c46d092018-04-07 15:32:371526
Paul Lewisca569a52020-09-09 16:11:511527 context.strokeStyle = ThemeSupport.ThemeSupport.instance().patchColorText('#eee', colorUsage.Background);
Blink Reformat4c46d092018-04-07 15:32:371528 context.beginPath();
Alexei Filippov5f6b11d2018-08-18 03:30:281529 this._forEachGroupInViewport((offset, index, group, isFirst) => {
Tim van der Lippe1d6e57a2019-09-30 11:55:341530 if (isFirst || group.style.padding < 4) {
Blink Reformat4c46d092018-04-07 15:32:371531 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341532 }
Blink Reformat4c46d092018-04-07 15:32:371533 hLine(offset - 2.5);
1534 });
1535 hLine(lastGroupOffset + 1.5);
1536 context.stroke();
1537
Alexei Filippov5f6b11d2018-08-18 03:30:281538 this._forEachGroupInViewport((offset, index, group) => {
Tim van der Lippe1d6e57a2019-09-30 11:55:341539 if (group.style.useFirstLineForOverview) {
Blink Reformat4c46d092018-04-07 15:32:371540 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341541 }
Blink Reformat4c46d092018-04-07 15:32:371542 if (!this._isGroupCollapsible(index) || group.expanded) {
Anubha Mathur72dd5822019-06-13 23:05:191543 if (!group.style.shareHeaderLine && this._isGroupFocused(index)) {
Blink Reformat4c46d092018-04-07 15:32:371544 context.fillStyle = group.style.backgroundColor;
1545 context.fillRect(0, offset, width, group.style.height);
1546 }
1547 return;
1548 }
Tim van der Lippe1d6e57a2019-09-30 11:55:341549 if (this._useWebGL) {
Alexei Filippov5f6b11d2018-08-18 03:30:281550 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341551 }
Blink Reformat4c46d092018-04-07 15:32:371552 let nextGroup = index + 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:341553 while (nextGroup < groups.length && groups[nextGroup].style.nestingLevel > group.style.nestingLevel) {
Blink Reformat4c46d092018-04-07 15:32:371554 nextGroup++;
Tim van der Lippe1d6e57a2019-09-30 11:55:341555 }
Blink Reformat4c46d092018-04-07 15:32:371556 const endLevel = nextGroup < groups.length ? groups[nextGroup].startLevel : this._dataProvider.maxStackDepth();
Alexei Filippov23428d42018-04-28 01:09:071557 this._drawCollapsedOverviewForGroup(group, offset, endLevel);
Blink Reformat4c46d092018-04-07 15:32:371558 });
1559
1560 context.save();
Alexei Filippov5f6b11d2018-08-18 03:30:281561 this._forEachGroupInViewport((offset, index, group) => {
Blink Reformat4c46d092018-04-07 15:32:371562 context.font = group.style.font;
1563 if (this._isGroupCollapsible(index) && !group.expanded || group.style.shareHeaderLine) {
1564 const width = this._labelWidthForGroup(context, group) + 2;
Tim van der Lippe1d6e57a2019-09-30 11:55:341565 if (this._isGroupFocused(index)) {
Blink Reformat4c46d092018-04-07 15:32:371566 context.fillStyle = this._selectedGroupBackroundColor;
Tim van der Lippe1d6e57a2019-09-30 11:55:341567 } else {
Tim van der Lippebb769172020-02-12 15:32:441568 context.fillStyle = Common.Color.Color.parse(group.style.backgroundColor).setAlpha(0.8).asString(null);
Tim van der Lippe1d6e57a2019-09-30 11:55:341569 }
Blink Reformat4c46d092018-04-07 15:32:371570
1571 context.fillRect(
1572 this._headerLeftPadding - this._headerLabelXPadding, offset + this._headerLabelYPadding, width,
1573 group.style.height - 2 * this._headerLabelYPadding);
1574 }
1575 context.fillStyle = group.style.color;
1576 context.fillText(
1577 group.name, Math.floor(this._expansionArrowIndent * (group.style.nestingLevel + 1) + this._arrowSide),
1578 offset + group.style.height - this._textBaseline);
1579 });
1580 context.restore();
1581
Paul Lewisca569a52020-09-09 16:11:511582 context.fillStyle = ThemeSupport.ThemeSupport.instance().patchColorText('#6e6e6e', colorUsage.Foreground);
Blink Reformat4c46d092018-04-07 15:32:371583 context.beginPath();
Alexei Filippov5f6b11d2018-08-18 03:30:281584 this._forEachGroupInViewport((offset, index, group) => {
Blink Reformat4c46d092018-04-07 15:32:371585 if (this._isGroupCollapsible(index)) {
1586 drawExpansionArrow.call(
1587 this, this._expansionArrowIndent * (group.style.nestingLevel + 1),
1588 offset + group.style.height - this._textBaseline - this._arrowSide / 2, !!group.expanded);
1589 }
1590 });
1591 context.fill();
1592
Paul Lewisca569a52020-09-09 16:11:511593 context.strokeStyle = ThemeSupport.ThemeSupport.instance().patchColorText('#ddd', colorUsage.Background);
Blink Reformat4c46d092018-04-07 15:32:371594 context.beginPath();
1595 context.stroke();
1596
Alexei Filippov5f6b11d2018-08-18 03:30:281597 this._forEachGroupInViewport((offset, index, group, isFirst, groupHeight) => {
Anubha Mathur72dd5822019-06-13 23:05:191598 if (this._isGroupFocused(index)) {
Alexei Filippov23428d42018-04-28 01:09:071599 const lineWidth = 2;
1600 const bracketLength = 10;
Blink Reformat4c46d092018-04-07 15:32:371601 context.fillStyle = this._selectedGroupBorderColor;
Alexei Filippov23428d42018-04-28 01:09:071602 context.fillRect(0, offset - lineWidth, lineWidth, groupHeight - group.style.padding + 2 * lineWidth);
1603 context.fillRect(0, offset - lineWidth, bracketLength, lineWidth);
1604 context.fillRect(0, offset + groupHeight - group.style.padding, bracketLength, lineWidth);
Blink Reformat4c46d092018-04-07 15:32:371605 }
1606 });
1607
1608 context.restore();
1609
1610 /**
1611 * @param {number} y
1612 */
1613 function hLine(y) {
1614 context.moveTo(0, y);
1615 context.lineTo(width, y);
1616 }
1617
1618 /**
1619 * @param {number} x
1620 * @param {number} y
1621 * @param {boolean} expanded
Tim van der Lippefd2b2ce2020-01-03 15:05:181622 * @this {FlameChart}
Blink Reformat4c46d092018-04-07 15:32:371623 */
1624 function drawExpansionArrow(x, y, expanded) {
1625 const arrowHeight = this._arrowSide * Math.sqrt(3) / 2;
1626 const arrowCenterOffset = Math.round(arrowHeight / 2);
1627 context.save();
1628 context.translate(x, y);
1629 context.rotate(expanded ? Math.PI / 2 : 0);
1630 context.moveTo(-arrowCenterOffset, -this._arrowSide / 2);
1631 context.lineTo(-arrowCenterOffset, this._arrowSide / 2);
1632 context.lineTo(arrowHeight - arrowCenterOffset, 0);
1633 context.restore();
1634 }
1635 }
1636
1637 /**
Tim van der Lippe8ef250c2020-02-20 16:29:251638 * @param {function(number, number, !Group, boolean, number)} callback
Blink Reformat4c46d092018-04-07 15:32:371639 */
1640 _forEachGroup(callback) {
Blink Reformat4c46d092018-04-07 15:32:371641 const groups = this._rawTimelineData.groups || [];
Tim van der Lippe1d6e57a2019-09-30 11:55:341642 if (!groups.length) {
Blink Reformat4c46d092018-04-07 15:32:371643 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341644 }
Blink Reformat4c46d092018-04-07 15:32:371645 const groupOffsets = this._groupOffsets;
1646 /** @type !Array<{nestingLevel: number, visible: boolean}> */
1647 const groupStack = [{nestingLevel: -1, visible: true}];
1648 for (let i = 0; i < groups.length; ++i) {
1649 const groupTop = groupOffsets[i];
1650 const group = groups[i];
Blink Reformat4c46d092018-04-07 15:32:371651 let firstGroup = true;
1652 while (groupStack.peekLast().nestingLevel >= group.style.nestingLevel) {
1653 groupStack.pop();
1654 firstGroup = false;
1655 }
1656 const parentGroupVisible = groupStack.peekLast().visible;
1657 const thisGroupVisible = parentGroupVisible && (!this._isGroupCollapsible(i) || group.expanded);
1658 groupStack.push({nestingLevel: group.style.nestingLevel, visible: thisGroupVisible});
Alexei Filippov23428d42018-04-28 01:09:071659 const nextOffset = i === groups.length - 1 ? groupOffsets[i + 1] + group.style.padding : groupOffsets[i + 1];
Tim van der Lippe1d6e57a2019-09-30 11:55:341660 if (!parentGroupVisible) {
Blink Reformat4c46d092018-04-07 15:32:371661 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341662 }
Blink Reformat4c46d092018-04-07 15:32:371663 callback(groupTop, i, group, firstGroup, nextOffset - groupTop);
1664 }
1665 }
1666
1667 /**
Tim van der Lippe8ef250c2020-02-20 16:29:251668 * @param {function(number, number, !Group, boolean, number)} callback
Alexei Filippov5f6b11d2018-08-18 03:30:281669 */
1670 _forEachGroupInViewport(callback) {
1671 const top = this._chartViewport.scrollOffset();
1672 this._forEachGroup((groupTop, index, group, firstGroup, height) => {
Tim van der Lippe1d6e57a2019-09-30 11:55:341673 if (groupTop - group.style.padding > top + this._offsetHeight) {
Alexei Filippov5f6b11d2018-08-18 03:30:281674 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341675 }
1676 if (groupTop + height < top) {
Alexei Filippov5f6b11d2018-08-18 03:30:281677 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341678 }
Alexei Filippov5f6b11d2018-08-18 03:30:281679 callback(groupTop, index, group, firstGroup, height);
1680 });
1681 }
1682
1683 /**
Blink Reformat4c46d092018-04-07 15:32:371684 * @param {!CanvasRenderingContext2D} context
Tim van der Lippe8ef250c2020-02-20 16:29:251685 * @param {!Group} group
Blink Reformat4c46d092018-04-07 15:32:371686 * @return {number}
1687 */
1688 _labelWidthForGroup(context, group) {
Tim van der Lippebb769172020-02-12 15:32:441689 return UI.UIUtils.measureTextWidth(context, group.name) +
1690 this._expansionArrowIndent * (group.style.nestingLevel + 1) + 2 * this._headerLabelXPadding;
Blink Reformat4c46d092018-04-07 15:32:371691 }
1692
1693 /**
Tim van der Lippe8ef250c2020-02-20 16:29:251694 * @param {!Group} group
Blink Reformat4c46d092018-04-07 15:32:371695 * @param {number} y
1696 * @param {number} endLevel
1697 */
1698 _drawCollapsedOverviewForGroup(group, y, endLevel) {
Tim van der Lippebb769172020-02-12 15:32:441699 const range = new Common.SegmentedRange.SegmentedRange(mergeCallback);
Blink Reformat4c46d092018-04-07 15:32:371700 const timeWindowLeft = this._chartViewport.windowLeftTime();
1701 const timeWindowRight = this._chartViewport.windowRightTime();
1702 const context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
1703 const barHeight = group.style.height;
1704 const entryStartTimes = this._rawTimelineData.entryStartTimes;
1705 const entryTotalTimes = this._rawTimelineData.entryTotalTimes;
1706 const timeToPixel = this._chartViewport.timeToPixel();
1707
1708 for (let level = group.startLevel; level < endLevel; ++level) {
1709 const levelIndexes = this._timelineLevels[level];
1710 const rightIndexOnLevel =
1711 levelIndexes.lowerBound(timeWindowRight, (time, entryIndex) => time - entryStartTimes[entryIndex]) - 1;
1712 let lastDrawOffset = Infinity;
1713
1714 for (let entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
1715 const entryIndex = levelIndexes[entryIndexOnLevel];
1716 const entryStartTime = entryStartTimes[entryIndex];
1717 const barX = this._timeToPositionClipped(entryStartTime);
1718 const entryEndTime = entryStartTime + entryTotalTimes[entryIndex];
Tim van der Lippe1d6e57a2019-09-30 11:55:341719 if (isNaN(entryEndTime) || barX >= lastDrawOffset) {
Blink Reformat4c46d092018-04-07 15:32:371720 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341721 }
1722 if (entryEndTime <= timeWindowLeft) {
Blink Reformat4c46d092018-04-07 15:32:371723 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:341724 }
Blink Reformat4c46d092018-04-07 15:32:371725 lastDrawOffset = barX;
Alexei Filippove5197622018-08-18 03:04:331726 const color = this._entryColorsCache[entryIndex];
Blink Reformat4c46d092018-04-07 15:32:371727 const endBarX = this._timeToPositionClipped(entryEndTime);
1728 if (group.style.useDecoratorsForOverview && this._dataProvider.forceDecoration(entryIndex)) {
1729 const unclippedBarX = this._chartViewport.timeToPosition(entryStartTime);
1730 const barWidth = endBarX - barX;
1731 context.beginPath();
1732 context.fillStyle = color;
1733 context.fillRect(barX, y, barWidth, barHeight - 1);
1734 this._dataProvider.decorateEntry(
1735 entryIndex, context, '', barX, y, barWidth, barHeight, unclippedBarX, timeToPixel);
1736 continue;
1737 }
Tim van der Lippebb769172020-02-12 15:32:441738 range.append(new Common.SegmentedRange.Segment(barX, endBarX, color));
Blink Reformat4c46d092018-04-07 15:32:371739 }
1740 }
1741
1742 const segments = range.segments().slice().sort((a, b) => a.data.localeCompare(b.data));
1743 let lastColor;
1744 context.beginPath();
1745 for (let i = 0; i < segments.length; ++i) {
1746 const segment = segments[i];
1747 if (lastColor !== segments[i].data) {
1748 context.fill();
1749 context.beginPath();
1750 lastColor = segments[i].data;
1751 context.fillStyle = lastColor;
1752 }
Alexei Filippov23428d42018-04-28 01:09:071753 context.rect(segment.begin, y, segment.end - segment.begin, barHeight);
Blink Reformat4c46d092018-04-07 15:32:371754 }
1755 context.fill();
1756
1757 /**
Tim van der Lippebb769172020-02-12 15:32:441758 * @param {!Common.SegmentedRange.Segment} a
1759 * @param {!Common.SegmentedRange.Segment} b
1760 * @return {?Common.SegmentedRange.Segment}
Blink Reformat4c46d092018-04-07 15:32:371761 */
1762 function mergeCallback(a, b) {
1763 return a.data === b.data && a.end + 0.4 > b.end ? a : null;
1764 }
1765 }
1766
1767 /**
1768 * @param {!CanvasRenderingContext2D} context
1769 * @param {number} height
1770 * @param {number} width
1771 */
1772 _drawFlowEvents(context, width, height) {
1773 context.save();
1774 const ratio = window.devicePixelRatio;
1775 const top = this._chartViewport.scrollOffset();
1776 const arrowWidth = 6;
1777 context.scale(ratio, ratio);
1778 context.translate(0, -top);
1779
1780 context.fillStyle = '#7f5050';
1781 context.strokeStyle = '#7f5050';
1782 const td = this._timelineData();
1783 const endIndex = td.flowStartTimes.lowerBound(this._chartViewport.windowRightTime());
1784
1785 context.lineWidth = 0.5;
1786 for (let i = 0; i < endIndex; ++i) {
Tim van der Lippe1d6e57a2019-09-30 11:55:341787 if (!td.flowEndTimes[i] || td.flowEndTimes[i] < this._chartViewport.windowLeftTime()) {
Blink Reformat4c46d092018-04-07 15:32:371788 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341789 }
Blink Reformat4c46d092018-04-07 15:32:371790 const startX = this._chartViewport.timeToPosition(td.flowStartTimes[i]);
1791 const endX = this._chartViewport.timeToPosition(td.flowEndTimes[i]);
1792 const startLevel = td.flowStartLevels[i];
1793 const endLevel = td.flowEndLevels[i];
1794 const startY = this._levelToOffset(startLevel) + this._levelHeight(startLevel) / 2;
1795 const endY = this._levelToOffset(endLevel) + this._levelHeight(endLevel) / 2;
1796
1797
1798 const segment = Math.min((endX - startX) / 4, 40);
1799 const distanceTime = td.flowEndTimes[i] - td.flowStartTimes[i];
1800 const distanceY = (endY - startY) / 10;
1801 const spread = 30;
1802 const lineY = distanceTime < 1 ? startY : spread + Math.max(0, startY + distanceY * (i % spread));
1803
1804 const p = [];
1805 p.push({x: startX, y: startY});
1806 p.push({x: startX + arrowWidth, y: startY});
1807 p.push({x: startX + segment + 2 * arrowWidth, y: startY});
1808 p.push({x: startX + segment, y: lineY});
1809 p.push({x: startX + segment * 2, y: lineY});
1810 p.push({x: endX - segment * 2, y: lineY});
1811 p.push({x: endX - segment, y: lineY});
1812 p.push({x: endX - segment - 2 * arrowWidth, y: endY});
1813 p.push({x: endX - arrowWidth, y: endY});
1814
1815 context.beginPath();
1816 context.moveTo(p[0].x, p[0].y);
1817 context.lineTo(p[1].x, p[1].y);
1818 context.bezierCurveTo(p[2].x, p[2].y, p[3].x, p[3].y, p[4].x, p[4].y);
1819 context.lineTo(p[5].x, p[5].y);
1820 context.bezierCurveTo(p[6].x, p[6].y, p[7].x, p[7].y, p[8].x, p[8].y);
1821 context.stroke();
1822
1823 context.beginPath();
1824 context.arc(startX, startY, 2, -Math.PI / 2, Math.PI / 2, false);
1825 context.fill();
1826
1827 context.beginPath();
1828 context.moveTo(endX, endY);
1829 context.lineTo(endX - arrowWidth, endY - 3);
1830 context.lineTo(endX - arrowWidth, endY + 3);
1831 context.fill();
1832 }
1833 context.restore();
1834 }
1835
1836 _drawMarkers() {
1837 const markers = this._timelineData().markers;
1838 const left = this._markerIndexBeforeTime(this.minimumBoundary());
1839 const rightBoundary = this.maximumBoundary();
1840 const timeToPixel = this._chartViewport.timeToPixel();
1841
1842 const context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
1843 context.save();
1844 const ratio = window.devicePixelRatio;
1845 context.scale(ratio, ratio);
1846 context.translate(0, 3);
Tim van der Lippefd2b2ce2020-01-03 15:05:181847 const height = HeaderHeight - 1;
Blink Reformat4c46d092018-04-07 15:32:371848 for (let i = left; i < markers.length; i++) {
1849 const timestamp = markers[i].startTime();
Tim van der Lippe1d6e57a2019-09-30 11:55:341850 if (timestamp > rightBoundary) {
Blink Reformat4c46d092018-04-07 15:32:371851 break;
Tim van der Lippe1d6e57a2019-09-30 11:55:341852 }
Blink Reformat4c46d092018-04-07 15:32:371853 markers[i].draw(context, this._chartViewport.timeToPosition(timestamp), height, timeToPixel);
1854 }
1855 context.restore();
1856 }
1857
1858 _updateMarkerHighlight() {
1859 const element = this._markerHighlighElement;
Tim van der Lippe1d6e57a2019-09-30 11:55:341860 if (element.parentElement) {
Blink Reformat4c46d092018-04-07 15:32:371861 element.remove();
Tim van der Lippe1d6e57a2019-09-30 11:55:341862 }
Blink Reformat4c46d092018-04-07 15:32:371863 const markerIndex = this._highlightedMarkerIndex;
Tim van der Lippe1d6e57a2019-09-30 11:55:341864 if (markerIndex === -1) {
Blink Reformat4c46d092018-04-07 15:32:371865 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:341866 }
Blink Reformat4c46d092018-04-07 15:32:371867 const marker = this._timelineData().markers[markerIndex];
1868 const barX = this._timeToPositionClipped(marker.startTime());
1869 element.title = marker.title();
1870 const style = element.style;
1871 style.left = barX + 'px';
1872 style.backgroundColor = marker.color();
1873 this._viewportElement.appendChild(element);
1874 }
1875
1876 /**
Tim van der Lippefd2b2ce2020-01-03 15:05:181877 * @param {?TimelineData} timelineData
Blink Reformat4c46d092018-04-07 15:32:371878 */
1879 _processTimelineData(timelineData) {
1880 if (!timelineData) {
1881 this._timelineLevels = null;
1882 this._visibleLevelOffsets = null;
1883 this._visibleLevels = null;
1884 this._groupOffsets = null;
1885 this._rawTimelineData = null;
Alexei Filippov57ccafb2018-08-14 20:59:051886 this._forceDecorationCache = null;
Alexei Filippove5197622018-08-18 03:04:331887 this._entryColorsCache = null;
Blink Reformat4c46d092018-04-07 15:32:371888 this._rawTimelineDataLength = 0;
1889 this._selectedGroup = -1;
Anubha Mathur72dd5822019-06-13 23:05:191890 this._keyboardFocusedGroup = -1;
Blink Reformat4c46d092018-04-07 15:32:371891 this._flameChartDelegate.updateSelectedGroup(this, null);
1892 return;
1893 }
1894
1895 this._rawTimelineData = timelineData;
1896 this._rawTimelineDataLength = timelineData.entryStartTimes.length;
Alexei Filippov57ccafb2018-08-14 20:59:051897 this._forceDecorationCache = new Int8Array(this._rawTimelineDataLength);
Alexei Filippove5197622018-08-18 03:04:331898 this._entryColorsCache = new Array(this._rawTimelineDataLength);
1899 for (let i = 0; i < this._rawTimelineDataLength; ++i) {
Alexei Filippov57ccafb2018-08-14 20:59:051900 this._forceDecorationCache[i] = this._dataProvider.forceDecoration(i) ? 1 : 0;
Alexei Filippove5197622018-08-18 03:04:331901 this._entryColorsCache[i] = this._dataProvider.entryColor(i);
1902 }
Blink Reformat4c46d092018-04-07 15:32:371903
1904 const entryCounters = new Uint32Array(this._dataProvider.maxStackDepth() + 1);
Tim van der Lippe1d6e57a2019-09-30 11:55:341905 for (let i = 0; i < timelineData.entryLevels.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:371906 ++entryCounters[timelineData.entryLevels[i]];
Tim van der Lippe1d6e57a2019-09-30 11:55:341907 }
Blink Reformat4c46d092018-04-07 15:32:371908 const levelIndexes = new Array(entryCounters.length);
1909 for (let i = 0; i < levelIndexes.length; ++i) {
1910 levelIndexes[i] = new Uint32Array(entryCounters[i]);
1911 entryCounters[i] = 0;
1912 }
Anubha Mathur72dd5822019-06-13 23:05:191913
Blink Reformat4c46d092018-04-07 15:32:371914 for (let i = 0; i < timelineData.entryLevels.length; ++i) {
1915 const level = timelineData.entryLevels[i];
1916 levelIndexes[level][entryCounters[level]++] = i;
1917 }
1918 this._timelineLevels = levelIndexes;
1919 const groups = this._rawTimelineData.groups || [];
1920 for (let i = 0; i < groups.length; ++i) {
1921 const expanded = this._groupExpansionState[groups[i].name];
Tim van der Lippe1d6e57a2019-09-30 11:55:341922 if (expanded !== undefined) {
Blink Reformat4c46d092018-04-07 15:32:371923 groups[i].expanded = expanded;
Tim van der Lippe1d6e57a2019-09-30 11:55:341924 }
Blink Reformat4c46d092018-04-07 15:32:371925 }
1926 this._updateLevelPositions();
1927 this._updateHeight();
1928
1929 this._selectedGroup = timelineData.selectedGroup ? groups.indexOf(timelineData.selectedGroup) : -1;
Anubha Mathur72dd5822019-06-13 23:05:191930 this._keyboardFocusedGroup = this._selectedGroup;
Blink Reformat4c46d092018-04-07 15:32:371931 this._flameChartDelegate.updateSelectedGroup(this, timelineData.selectedGroup);
1932 }
1933
1934 _updateLevelPositions() {
1935 const levelCount = this._dataProvider.maxStackDepth();
1936 const groups = this._rawTimelineData.groups || [];
1937 this._visibleLevelOffsets = new Uint32Array(levelCount + 1);
1938 this._visibleLevelHeights = new Uint32Array(levelCount);
1939 this._visibleLevels = new Uint16Array(levelCount);
1940 this._groupOffsets = new Uint32Array(groups.length + 1);
1941
1942 let groupIndex = -1;
Tim van der Lippefd2b2ce2020-01-03 15:05:181943 let currentOffset = this._rulerEnabled ? HeaderHeight + 2 : 2;
Blink Reformat4c46d092018-04-07 15:32:371944 let visible = true;
1945 /** @type !Array<{nestingLevel: number, visible: boolean}> */
1946 const groupStack = [{nestingLevel: -1, visible: true}];
1947 const lastGroupLevel = Math.max(levelCount, groups.length ? groups.peekLast().startLevel + 1 : 0);
1948 let level;
1949 for (level = 0; level < lastGroupLevel; ++level) {
1950 let parentGroupIsVisible = true;
1951 let style;
1952 while (groupIndex < groups.length - 1 && level === groups[groupIndex + 1].startLevel) {
1953 ++groupIndex;
1954 style = groups[groupIndex].style;
1955 let nextLevel = true;
1956 while (groupStack.peekLast().nestingLevel >= style.nestingLevel) {
1957 groupStack.pop();
1958 nextLevel = false;
1959 }
1960 const thisGroupIsVisible =
1961 groupIndex >= 0 && this._isGroupCollapsible(groupIndex) ? groups[groupIndex].expanded : true;
1962 parentGroupIsVisible = groupStack.peekLast().visible;
1963 visible = thisGroupIsVisible && parentGroupIsVisible;
1964 groupStack.push({nestingLevel: style.nestingLevel, visible: visible});
Tim van der Lippe1d6e57a2019-09-30 11:55:341965 if (parentGroupIsVisible) {
Blink Reformat4c46d092018-04-07 15:32:371966 currentOffset += nextLevel ? 0 : style.padding;
Tim van der Lippe1d6e57a2019-09-30 11:55:341967 }
Blink Reformat4c46d092018-04-07 15:32:371968 this._groupOffsets[groupIndex] = currentOffset;
Tim van der Lippe1d6e57a2019-09-30 11:55:341969 if (parentGroupIsVisible && !style.shareHeaderLine) {
Blink Reformat4c46d092018-04-07 15:32:371970 currentOffset += style.height;
Tim van der Lippe1d6e57a2019-09-30 11:55:341971 }
Blink Reformat4c46d092018-04-07 15:32:371972 }
Tim van der Lippe1d6e57a2019-09-30 11:55:341973 if (level >= levelCount) {
Yang Guo8fb3ac52019-07-31 20:17:501974 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:341975 }
Blink Reformat4c46d092018-04-07 15:32:371976 const isFirstOnLevel = groupIndex >= 0 && level === groups[groupIndex].startLevel;
1977 const thisLevelIsVisible =
1978 parentGroupIsVisible && (visible || isFirstOnLevel && groups[groupIndex].style.useFirstLineForOverview);
Yang Guo8fb3ac52019-07-31 20:17:501979 let height;
1980 if (groupIndex >= 0) {
1981 const group = groups[groupIndex];
1982 const styleB = group.style;
1983 height = isFirstOnLevel && !styleB.shareHeaderLine || (styleB.collapsible && !group.expanded) ?
1984 styleB.height :
1985 (styleB.itemsHeight || this._barHeight);
1986 } else {
1987 height = this._barHeight;
Blink Reformat4c46d092018-04-07 15:32:371988 }
Yang Guo8fb3ac52019-07-31 20:17:501989 this._visibleLevels[level] = thisLevelIsVisible;
1990 this._visibleLevelOffsets[level] = currentOffset;
1991 this._visibleLevelHeights[level] = height;
Tim van der Lippe1d6e57a2019-09-30 11:55:341992 if (thisLevelIsVisible || (parentGroupIsVisible && style && style.shareHeaderLine && isFirstOnLevel)) {
Blink Reformat4c46d092018-04-07 15:32:371993 currentOffset += this._visibleLevelHeights[level];
Tim van der Lippe1d6e57a2019-09-30 11:55:341994 }
Blink Reformat4c46d092018-04-07 15:32:371995 }
Tim van der Lippe1d6e57a2019-09-30 11:55:341996 if (groupIndex >= 0) {
Blink Reformat4c46d092018-04-07 15:32:371997 this._groupOffsets[groupIndex + 1] = currentOffset;
Tim van der Lippe1d6e57a2019-09-30 11:55:341998 }
Blink Reformat4c46d092018-04-07 15:32:371999 this._visibleLevelOffsets[level] = currentOffset;
Tim van der Lippe1d6e57a2019-09-30 11:55:342000 if (this._useWebGL) {
Alexei Filippov57ccafb2018-08-14 20:59:052001 this._setupGLGeometry();
Tim van der Lippe1d6e57a2019-09-30 11:55:342002 }
Blink Reformat4c46d092018-04-07 15:32:372003 }
2004
2005 /**
2006 * @param {number} index
2007 */
2008 _isGroupCollapsible(index) {
2009 const groups = this._rawTimelineData.groups || [];
2010 const style = groups[index].style;
Tim van der Lippe1d6e57a2019-09-30 11:55:342011 if (!style.shareHeaderLine || !style.collapsible) {
Blink Reformat4c46d092018-04-07 15:32:372012 return !!style.collapsible;
Tim van der Lippe1d6e57a2019-09-30 11:55:342013 }
Blink Reformat4c46d092018-04-07 15:32:372014 const isLastGroup = index + 1 >= groups.length;
Tim van der Lippe1d6e57a2019-09-30 11:55:342015 if (!isLastGroup && groups[index + 1].style.nestingLevel > style.nestingLevel) {
Blink Reformat4c46d092018-04-07 15:32:372016 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:342017 }
Blink Reformat4c46d092018-04-07 15:32:372018 const nextGroupLevel = isLastGroup ? this._dataProvider.maxStackDepth() : groups[index + 1].startLevel;
Tim van der Lippe1d6e57a2019-09-30 11:55:342019 if (nextGroupLevel !== groups[index].startLevel + 1) {
Blink Reformat4c46d092018-04-07 15:32:372020 return true;
Tim van der Lippe1d6e57a2019-09-30 11:55:342021 }
Blink Reformat4c46d092018-04-07 15:32:372022 // For groups that only have one line and share header line, pretend these are not collapsible
2023 // unless the itemsHeight does not match the headerHeight
2024 return style.height !== style.itemsHeight;
2025 }
2026
2027 /**
2028 * @param {number} entryIndex
2029 */
2030 setSelectedEntry(entryIndex) {
Tim van der Lippe1d6e57a2019-09-30 11:55:342031 if (this._selectedEntryIndex === entryIndex) {
Blink Reformat4c46d092018-04-07 15:32:372032 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:342033 }
2034 if (entryIndex !== -1) {
Blink Reformat4c46d092018-04-07 15:32:372035 this._chartViewport.hideRangeSelection();
Tim van der Lippe1d6e57a2019-09-30 11:55:342036 }
Blink Reformat4c46d092018-04-07 15:32:372037 this._selectedEntryIndex = entryIndex;
2038 this._revealEntry(entryIndex);
2039 this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
2040 }
2041
2042 /**
2043 * @param {!Element} element
2044 * @param {number} entryIndex
2045 */
2046 _updateElementPosition(element, entryIndex) {
Alexei Filippov72d792d2018-11-06 07:15:042047 const elementMinWidthPx = 2;
2048 element.classList.add('hidden');
Tim van der Lippe1d6e57a2019-09-30 11:55:342049 if (entryIndex === -1) {
Blink Reformat4c46d092018-04-07 15:32:372050 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:342051 }
Blink Reformat4c46d092018-04-07 15:32:372052 const timelineData = this._timelineData();
2053 const startTime = timelineData.entryStartTimes[entryIndex];
Alexei Filippov72d792d2018-11-06 07:15:042054 const duration = timelineData.entryTotalTimes[entryIndex];
Alexei Filippov65106502018-11-29 05:16:192055 let barX = 0;
2056 let barWidth = 0;
2057 let visible = true;
Alexei Filippov6c622e92018-11-10 02:13:592058 if (Number.isNaN(duration)) {
2059 const position = this._markerPositions.get(entryIndex);
Alexei Filippov65106502018-11-29 05:16:192060 if (position) {
2061 barX = position.x;
2062 barWidth = position.width;
2063 } else {
2064 visible = false;
2065 }
Alexei Filippov6c622e92018-11-10 02:13:592066 } else {
2067 barX = this._chartViewport.timeToPosition(startTime);
2068 barWidth = duration * this._chartViewport.timeToPixel();
2069 }
Tim van der Lippe1d6e57a2019-09-30 11:55:342070 if (barX + barWidth <= 0 || barX >= this._offsetWidth) {
Blink Reformat4c46d092018-04-07 15:32:372071 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:342072 }
Blink Reformat4c46d092018-04-07 15:32:372073 const barCenter = barX + barWidth / 2;
2074 barWidth = Math.max(barWidth, elementMinWidthPx);
2075 barX = barCenter - barWidth / 2;
2076 const entryLevel = timelineData.entryLevels[entryIndex];
2077 const barY = this._levelToOffset(entryLevel) - this._chartViewport.scrollOffset();
2078 const barHeight = this._levelHeight(entryLevel);
2079 const style = element.style;
2080 style.left = barX + 'px';
2081 style.top = barY + 'px';
2082 style.width = barWidth + 'px';
2083 style.height = barHeight - 1 + 'px';
Alexei Filippov65106502018-11-29 05:16:192084 element.classList.toggle('hidden', !visible);
Blink Reformat4c46d092018-04-07 15:32:372085 this._viewportElement.appendChild(element);
2086 }
2087
2088 /**
2089 * @param {number} time
2090 * @return {number}
2091 */
2092 _timeToPositionClipped(time) {
Jack Franklin1be909c2020-03-04 08:57:412093 return Platform.NumberUtilities.clamp(this._chartViewport.timeToPosition(time), 0, this._offsetWidth);
Blink Reformat4c46d092018-04-07 15:32:372094 }
2095
2096 /**
2097 * @param {number} level
2098 * @return {number}
2099 */
2100 _levelToOffset(level) {
2101 return this._visibleLevelOffsets[level];
2102 }
2103
2104 /**
2105 * @param {number} level
2106 * @return {number}
2107 */
2108 _levelHeight(level) {
2109 return this._visibleLevelHeights[level];
2110 }
2111
2112 _updateBoundaries() {
2113 this._totalTime = this._dataProvider.totalTime();
2114 this._minimumBoundary = this._dataProvider.minimumBoundary();
2115 this._chartViewport.setBoundaries(this._minimumBoundary, this._totalTime);
2116 }
2117
2118 _updateHeight() {
Alexei Filippov23428d42018-04-28 01:09:072119 const height = this._levelToOffset(this._dataProvider.maxStackDepth()) + 2;
Blink Reformat4c46d092018-04-07 15:32:372120 this._chartViewport.setContentHeight(height);
2121 }
2122
2123 /**
2124 * @override
2125 */
2126 onResize() {
2127 this.scheduleUpdate();
2128 }
2129
2130 /**
2131 * @override
2132 */
2133 update() {
Tim van der Lippe1d6e57a2019-09-30 11:55:342134 if (!this._timelineData()) {
Blink Reformat4c46d092018-04-07 15:32:372135 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:342136 }
Blink Reformat4c46d092018-04-07 15:32:372137 this._resetCanvas();
2138 this._updateHeight();
2139 this._updateBoundaries();
2140 this._draw();
Tim van der Lippe1d6e57a2019-09-30 11:55:342141 if (!this._chartViewport.isDragging()) {
Blink Reformat4c46d092018-04-07 15:32:372142 this._updateHighlight();
Tim van der Lippe1d6e57a2019-09-30 11:55:342143 }
Blink Reformat4c46d092018-04-07 15:32:372144 }
2145
2146 reset() {
2147 this._chartViewport.reset();
2148 this._rawTimelineData = null;
2149 this._rawTimelineDataLength = 0;
2150 this._highlightedMarkerIndex = -1;
2151 this._highlightedEntryIndex = -1;
2152 this._selectedEntryIndex = -1;
2153 /** @type {!Map<string,!Map<string,number>>} */
2154 this._textWidth = new Map();
2155 this._chartViewport.scheduleUpdate();
2156 }
2157
2158 scheduleUpdate() {
2159 this._chartViewport.scheduleUpdate();
2160 }
2161
2162 _enabled() {
2163 return this._rawTimelineDataLength !== 0;
2164 }
2165
2166 /**
2167 * @override
2168 * @param {number} time
2169 * @return {number}
2170 */
2171 computePosition(time) {
2172 return this._chartViewport.timeToPosition(time);
2173 }
2174
2175 /**
2176 * @override
2177 * @param {number} value
2178 * @param {number=} precision
2179 * @return {string}
2180 */
2181 formatValue(value, precision) {
2182 return this._dataProvider.formatValue(value - this.zeroTime(), precision);
2183 }
2184
2185 /**
2186 * @override
2187 * @return {number}
2188 */
2189 maximumBoundary() {
2190 return this._chartViewport.windowRightTime();
2191 }
2192
2193 /**
2194 * @override
2195 * @return {number}
2196 */
2197 minimumBoundary() {
2198 return this._chartViewport.windowLeftTime();
2199 }
2200
2201 /**
2202 * @override
2203 * @return {number}
2204 */
2205 zeroTime() {
2206 return this._dataProvider.minimumBoundary();
2207 }
2208
2209 /**
2210 * @override
2211 * @return {number}
2212 */
2213 boundarySpan() {
Alexei Filippov2578eb02018-04-11 08:15:052214 return this.maximumBoundary() - this.minimumBoundary();
Blink Reformat4c46d092018-04-07 15:32:372215 }
Tim van der Lippefd2b2ce2020-01-03 15:05:182216}
Blink Reformat4c46d092018-04-07 15:32:372217
Tim van der Lippefd2b2ce2020-01-03 15:05:182218export const HeaderHeight = 15;
2219export const MinimalTimeWindowMs = 0.5;
Blink Reformat4c46d092018-04-07 15:32:372220
2221/**
2222 * @unrestricted
2223 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182224export class TimelineData {
Blink Reformat4c46d092018-04-07 15:32:372225 /**
2226 * @param {!Array<number>|!Uint16Array} entryLevels
2227 * @param {!Array<number>|!Float32Array} entryTotalTimes
2228 * @param {!Array<number>|!Float64Array} entryStartTimes
Tim van der Lippe8ef250c2020-02-20 16:29:252229 * @param {?Array<!Group>} groups
Blink Reformat4c46d092018-04-07 15:32:372230 */
2231 constructor(entryLevels, entryTotalTimes, entryStartTimes, groups) {
2232 this.entryLevels = entryLevels;
2233 this.entryTotalTimes = entryTotalTimes;
2234 this.entryStartTimes = entryStartTimes;
2235 this.groups = groups;
Tim van der Lippefd2b2ce2020-01-03 15:05:182236 /** @type {!Array.<!FlameChartMarker>} */
Blink Reformat4c46d092018-04-07 15:32:372237 this.markers = [];
2238 this.flowStartTimes = [];
2239 this.flowStartLevels = [];
2240 this.flowEndTimes = [];
2241 this.flowEndLevels = [];
Tim van der Lippe8ef250c2020-02-20 16:29:252242 /** @type {?Group} */
Blink Reformat4c46d092018-04-07 15:32:372243 this.selectedGroup = null;
2244 }
Tim van der Lippefd2b2ce2020-01-03 15:05:182245}
Blink Reformat4c46d092018-04-07 15:32:372246
Tim van der Lippefd2b2ce2020-01-03 15:05:182247/**
2248 * @interface
2249 */
2250export class FlameChartDataProvider {
Blink Reformat4c46d092018-04-07 15:32:372251 /**
2252 * @return {number}
2253 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182254 minimumBoundary() {
2255 }
Blink Reformat4c46d092018-04-07 15:32:372256
2257 /**
2258 * @return {number}
2259 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182260 totalTime() {
2261 }
Blink Reformat4c46d092018-04-07 15:32:372262
2263 /**
2264 * @param {number} value
2265 * @param {number=} precision
2266 * @return {string}
2267 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182268 formatValue(value, precision) {
2269 }
Blink Reformat4c46d092018-04-07 15:32:372270
2271 /**
2272 * @return {number}
2273 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182274 maxStackDepth() {
2275 }
Blink Reformat4c46d092018-04-07 15:32:372276
2277 /**
Tim van der Lippefd2b2ce2020-01-03 15:05:182278 * @return {?TimelineData}
Blink Reformat4c46d092018-04-07 15:32:372279 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182280 timelineData() {
2281 }
Blink Reformat4c46d092018-04-07 15:32:372282
2283 /**
2284 * @param {number} entryIndex
2285 * @return {?Element}
2286 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182287 prepareHighlightedEntryInfo(entryIndex) {
2288 }
Blink Reformat4c46d092018-04-07 15:32:372289
2290 /**
2291 * @param {number} entryIndex
2292 * @return {boolean}
2293 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182294 canJumpToEntry(entryIndex) {
2295 }
Blink Reformat4c46d092018-04-07 15:32:372296
2297 /**
2298 * @param {number} entryIndex
2299 * @return {?string}
2300 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182301 entryTitle(entryIndex) {
2302 }
Blink Reformat4c46d092018-04-07 15:32:372303
2304 /**
2305 * @param {number} entryIndex
2306 * @return {?string}
2307 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182308 entryFont(entryIndex) {
2309 }
Blink Reformat4c46d092018-04-07 15:32:372310
2311 /**
2312 * @param {number} entryIndex
2313 * @return {string}
2314 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182315 entryColor(entryIndex) {
2316 }
Blink Reformat4c46d092018-04-07 15:32:372317
2318 /**
2319 * @param {number} entryIndex
2320 * @param {!CanvasRenderingContext2D} context
2321 * @param {?string} text
2322 * @param {number} barX
2323 * @param {number} barY
2324 * @param {number} barWidth
2325 * @param {number} barHeight
2326 * @param {number} unclippedBarX
2327 * @param {number} timeToPixelRatio
2328 * @return {boolean}
2329 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182330 decorateEntry(entryIndex, context, text, barX, barY, barWidth, barHeight, unclippedBarX, timeToPixelRatio) {
2331 }
Blink Reformat4c46d092018-04-07 15:32:372332
2333 /**
2334 * @param {number} entryIndex
2335 * @return {boolean}
2336 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182337 forceDecoration(entryIndex) {
2338 }
Blink Reformat4c46d092018-04-07 15:32:372339
2340 /**
2341 * @param {number} entryIndex
2342 * @return {string}
2343 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182344 textColor(entryIndex) {
2345 }
Paul Lewisead45752020-06-23 09:51:362346
2347 /**
2348 * @return {!Map<string, !SDK.TracingModel.Event>}
2349 */
2350 navStartTimes() {
2351 }
Tim van der Lippefd2b2ce2020-01-03 15:05:182352}
Blink Reformat4c46d092018-04-07 15:32:372353
2354/**
2355 * @interface
2356 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182357export class FlameChartMarker {
Blink Reformat4c46d092018-04-07 15:32:372358 /**
2359 * @return {number}
2360 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182361 startTime() {
2362 }
Blink Reformat4c46d092018-04-07 15:32:372363
2364 /**
2365 * @return {string}
2366 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182367 color() {
2368 }
Blink Reformat4c46d092018-04-07 15:32:372369
2370 /**
Alexei Filippov72d792d2018-11-06 07:15:042371 * @return {?string}
Blink Reformat4c46d092018-04-07 15:32:372372 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182373 title() {
2374 }
Blink Reformat4c46d092018-04-07 15:32:372375
2376 /**
2377 * @param {!CanvasRenderingContext2D} context
2378 * @param {number} x
2379 * @param {number} height
2380 * @param {number} pixelsPerMillisecond
2381 */
Tim van der Lippefd2b2ce2020-01-03 15:05:182382 draw(context, x, height, pixelsPerMillisecond) {
2383 }
2384}
Blink Reformat4c46d092018-04-07 15:32:372385
2386/** @enum {symbol} */
Tim van der Lippefd2b2ce2020-01-03 15:05:182387export const Events = {
Michael Liao712bbc22019-10-15 19:21:512388 CanvasFocused: Symbol('CanvasFocused'),
2389 EntryInvoked: Symbol('EntryInvoked'),
Blink Reformat4c46d092018-04-07 15:32:372390 EntrySelected: Symbol('EntrySelected'),
2391 EntryHighlighted: Symbol('EntryHighlighted')
2392};
2393
Tim van der Lippefd2b2ce2020-01-03 15:05:182394export const Colors = {
Blink Reformat4c46d092018-04-07 15:32:372395 SelectedGroupBackground: 'hsl(215, 85%, 98%)',
2396 SelectedGroupBorder: 'hsl(216, 68%, 54%)',
2397};
Tim van der Lippecec9b762020-02-13 15:31:222398
2399/**
2400 * @typedef {!{
2401 * name: string,
2402 * startLevel: number,
2403 * expanded: (boolean|undefined),
2404 * selectable: (boolean|undefined),
2405 * style: !GroupStyle
2406 * }}
2407 */
2408export let Group;
2409
2410/**
2411 * @typedef {!{
2412 * height: number,
2413 * padding: number,
2414 * collapsible: boolean,
2415 * font: string,
2416 * color: string,
2417 * backgroundColor: string,
2418 * nestingLevel: number,
2419 * itemsHeight: (number|undefined),
2420 * shareHeaderLine: (boolean|undefined),
2421 * useFirstLineForOverview: (boolean|undefined),
2422 * useDecoratorsForOverview: (boolean|undefined)
2423 * }}
2424 */
2425export let GroupStyle;