Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(296)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/ui_lazy/FlameChart.js

Issue 2466123002: DevTools: reformat front-end code to match chromium style. (Closed)
Patch Set: all done Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /** 1 /**
2 * Copyright (C) 2013 Google Inc. All rights reserved. 2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 * 3 *
4 * Redistribution and use in source and binary forms, with or without 4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are 5 * modification, are permitted provided that the following conditions are
6 * met: 6 * met:
7 * 7 *
8 * * Redistributions of source code must retain the above copyright 8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer. 9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above 10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer 11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the 12 * in the documentation and/or other materials provided with the
13 * distribution. 13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its 14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from 15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission. 16 * this software without specific prior written permission.
17 * 17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 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. 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */ 29 */
30
31 /** 30 /**
32 * @interface 31 * @interface
33 */ 32 */
34 WebInspector.FlameChartDelegate = function() { }; 33 WebInspector.FlameChartDelegate = function() {};
35 34
36 WebInspector.FlameChartDelegate.prototype = { 35 WebInspector.FlameChartDelegate.prototype = {
37 /** 36 /**
38 * @param {number} startTime 37 * @param {number} startTime
39 * @param {number} endTime 38 * @param {number} endTime
40 */ 39 */
41 requestWindowTimes: function(startTime, endTime) { }, 40 requestWindowTimes: function(startTime, endTime) {},
42 41
43 /** 42 /**
44 * @param {number} startTime 43 * @param {number} startTime
45 * @param {number} endTime 44 * @param {number} endTime
46 */ 45 */
47 updateRangeSelection: function(startTime, endTime) { }, 46 updateRangeSelection: function(startTime, endTime) {},
48 }; 47 };
49 48
50 /** 49 /**
51 * @constructor 50 * @unrestricted
52 * @extends {WebInspector.ChartViewport}
53 * @param {!WebInspector.FlameChartDataProvider} dataProvider
54 * @param {!WebInspector.FlameChartDelegate} flameChartDelegate
55 * @param {!WebInspector.Setting=} groupExpansionSetting
56 */ 51 */
57 WebInspector.FlameChart = function(dataProvider, flameChartDelegate, groupExpans ionSetting) 52 WebInspector.FlameChart = class extends WebInspector.ChartViewport {
58 { 53 /**
59 WebInspector.ChartViewport.call(this); 54 * @param {!WebInspector.FlameChartDataProvider} dataProvider
60 this.registerRequiredCSS("ui_lazy/flameChart.css"); 55 * @param {!WebInspector.FlameChartDelegate} flameChartDelegate
61 this.contentElement.classList.add("flame-chart-main-pane"); 56 * @param {!WebInspector.Setting=} groupExpansionSetting
57 */
58 constructor(dataProvider, flameChartDelegate, groupExpansionSetting) {
59 super();
60 this.registerRequiredCSS('ui_lazy/flameChart.css');
61 this.contentElement.classList.add('flame-chart-main-pane');
62 this._flameChartDelegate = flameChartDelegate; 62 this._flameChartDelegate = flameChartDelegate;
63 this._groupExpansionSetting = groupExpansionSetting; 63 this._groupExpansionSetting = groupExpansionSetting;
64 this._groupExpansionState = groupExpansionSetting && groupExpansionSetting.g et() || {}; 64 this._groupExpansionState = groupExpansionSetting && groupExpansionSetting.g et() || {};
65 65
66 this._dataProvider = dataProvider; 66 this._dataProvider = dataProvider;
67 this._calculator = new WebInspector.FlameChart.Calculator(dataProvider); 67 this._calculator = new WebInspector.FlameChart.Calculator(dataProvider);
68 68
69 this._canvas = /** @type {!HTMLCanvasElement} */ (this.viewportElement.creat eChild("canvas")); 69 this._canvas = /** @type {!HTMLCanvasElement} */ (this.viewportElement.creat eChild('canvas'));
70 this._canvas.tabIndex = 1; 70 this._canvas.tabIndex = 1;
71 this.setDefaultFocusedElement(this._canvas); 71 this.setDefaultFocusedElement(this._canvas);
72 this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), fal se); 72 this._canvas.addEventListener('mousemove', this._onMouseMove.bind(this), fal se);
73 this._canvas.addEventListener("mouseout", this._onMouseOut.bind(this), false ); 73 this._canvas.addEventListener('mouseout', this._onMouseOut.bind(this), false );
74 this._canvas.addEventListener("click", this._onClick.bind(this), false); 74 this._canvas.addEventListener('click', this._onClick.bind(this), false);
75 this._canvas.addEventListener("keydown", this._onKeyDown.bind(this), false); 75 this._canvas.addEventListener('keydown', this._onKeyDown.bind(this), false);
76 76
77 this._entryInfo = this.viewportElement.createChild("div", "flame-chart-entry -info"); 77 this._entryInfo = this.viewportElement.createChild('div', 'flame-chart-entry -info');
78 this._markerHighlighElement = this.viewportElement.createChild("div", "flame -chart-marker-highlight-element"); 78 this._markerHighlighElement = this.viewportElement.createChild('div', 'flame -chart-marker-highlight-element');
79 this._highlightElement = this.viewportElement.createChild("div", "flame-char t-highlight-element"); 79 this._highlightElement = this.viewportElement.createChild('div', 'flame-char t-highlight-element');
80 this._selectedElement = this.viewportElement.createChild("div", "flame-chart -selected-element"); 80 this._selectedElement = this.viewportElement.createChild('div', 'flame-chart -selected-element');
81 81
82 this._windowLeft = 0.0; 82 this._windowLeft = 0.0;
83 this._windowRight = 1.0; 83 this._windowRight = 1.0;
84 this._timeWindowLeft = 0; 84 this._timeWindowLeft = 0;
85 this._timeWindowRight = Infinity; 85 this._timeWindowRight = Infinity;
86 this._rangeSelectionStart = 0; 86 this._rangeSelectionStart = 0;
87 this._rangeSelectionEnd = 0; 87 this._rangeSelectionEnd = 0;
88 this._barHeight = dataProvider.barHeight(); 88 this._barHeight = dataProvider.barHeight();
89 this._paddingLeft = this._dataProvider.paddingLeft(); 89 this._paddingLeft = this._dataProvider.paddingLeft();
90 var markerPadding = 2; 90 var markerPadding = 2;
(...skipping 11 matching lines...) Expand all
102 this._headerLabelYPadding = 2; 102 this._headerLabelYPadding = 2;
103 103
104 this._highlightedMarkerIndex = -1; 104 this._highlightedMarkerIndex = -1;
105 this._highlightedEntryIndex = -1; 105 this._highlightedEntryIndex = -1;
106 this._selectedEntryIndex = -1; 106 this._selectedEntryIndex = -1;
107 this._rawTimelineDataLength = 0; 107 this._rawTimelineDataLength = 0;
108 /** @type {!Map<string,!Map<string,number>>} */ 108 /** @type {!Map<string,!Map<string,number>>} */
109 this._textWidth = new Map(); 109 this._textWidth = new Map();
110 110
111 this._lastMouseOffsetX = 0; 111 this._lastMouseOffsetX = 0;
112 }
113
114 /**
115 * @override
116 */
117 willHide() {
118 this.hideHighlight();
119 }
120
121 /**
122 * @param {number} entryIndex
123 */
124 highlightEntry(entryIndex) {
125 if (this._highlightedEntryIndex === entryIndex)
126 return;
127 this._highlightedEntryIndex = entryIndex;
128 this._updateElementPosition(this._highlightElement, this._highlightedEntryIn dex);
129 }
130
131 hideHighlight() {
132 this._entryInfo.removeChildren();
133 this._highlightedEntryIndex = -1;
134 this._updateElementPosition(this._highlightElement, this._highlightedEntryIn dex);
135 }
136
137 _resetCanvas() {
138 var ratio = window.devicePixelRatio;
139 this._canvas.width = this._offsetWidth * ratio;
140 this._canvas.height = this._offsetHeight * ratio;
141 this._canvas.style.width = this._offsetWidth + 'px';
142 this._canvas.style.height = this._offsetHeight + 'px';
143 }
144
145 /**
146 * @return {?WebInspector.FlameChart.TimelineData}
147 */
148 _timelineData() {
149 if (!this._dataProvider)
150 return null;
151 var timelineData = this._dataProvider.timelineData();
152 if (timelineData !== this._rawTimelineData || timelineData.entryStartTimes.l ength !== this._rawTimelineDataLength)
153 this._processTimelineData(timelineData);
154 return this._rawTimelineData;
155 }
156
157 /**
158 * @param {number} entryIndex
159 */
160 _revealEntry(entryIndex) {
161 var timelineData = this._timelineData();
162 if (!timelineData)
163 return;
164 // Think in terms of not where we are, but where we'll be after animation (i f present)
165 var timeLeft = this._cancelWindowTimesAnimation ? this._pendingAnimationTime Left : this._timeWindowLeft;
166 var timeRight = this._cancelWindowTimesAnimation ? this._pendingAnimationTim eRight : this._timeWindowRight;
167 var entryStartTime = timelineData.entryStartTimes[entryIndex];
168 var entryTotalTime = timelineData.entryTotalTimes[entryIndex];
169 var entryEndTime = entryStartTime + entryTotalTime;
170 var minEntryTimeWindow = Math.min(entryTotalTime, timeRight - timeLeft);
171
172 var y = this._levelToHeight(timelineData.entryLevels[entryIndex]);
173 this.setScrollOffset(y, this._barHeight);
174
175 if (timeLeft > entryEndTime) {
176 var delta = timeLeft - entryEndTime + minEntryTimeWindow;
177 this._flameChartDelegate.requestWindowTimes(timeLeft - delta, timeRight - delta);
178 } else if (timeRight < entryStartTime) {
179 var delta = entryStartTime - timeRight + minEntryTimeWindow;
180 this._flameChartDelegate.requestWindowTimes(timeLeft + delta, timeRight + delta);
181 }
182 }
183
184 /**
185 * @override
186 * @param {number} startTime
187 * @param {number} endTime
188 */
189 setWindowTimes(startTime, endTime) {
190 super.setWindowTimes(startTime, endTime);
191 this._updateHighlight();
192 }
193
194 /**
195 * @param {!Event} event
196 */
197 _onMouseMove(event) {
198 this._lastMouseOffsetX = event.offsetX;
199 this._lastMouseOffsetY = event.offsetY;
200 if (!this._enabled())
201 return;
202 if (this.isDragging())
203 return;
204 if (this._coordinatesToGroupIndex(event.offsetX, event.offsetY) >= 0) {
205 this.hideHighlight();
206 this.viewportElement.style.cursor = 'pointer';
207 return;
208 }
209 this._updateHighlight();
210 }
211
212 _updateHighlight() {
213 var inDividersBar = this._lastMouseOffsetY < WebInspector.FlameChart.Divider sBarHeight;
214 this._highlightedMarkerIndex = inDividersBar ? this._markerIndexAtPosition(t his._lastMouseOffsetX) : -1;
215 this._updateMarkerHighlight();
216
217 var entryIndex = this._coordinatesToEntryIndex(this._lastMouseOffsetX, this. _lastMouseOffsetY);
218 if (entryIndex === -1) {
219 this.hideHighlight();
220 return;
221 }
222 if (this.isDragging())
223 return;
224 this._updatePopover(entryIndex);
225 this.viewportElement.style.cursor = this._dataProvider.canJumpToEntry(entryI ndex) ? 'pointer' : 'default';
226 this.highlightEntry(entryIndex);
227 }
228
229 _onMouseOut() {
230 this._lastMouseOffsetX = -1;
231 this._lastMouseOffsetY = -1;
232 this.hideHighlight();
233 }
234
235 /**
236 * @param {number} entryIndex
237 */
238 _updatePopover(entryIndex) {
239 if (entryIndex === this._highlightedEntryIndex) {
240 this._updatePopoverOffset();
241 return;
242 }
243 this._entryInfo.removeChildren();
244 var popoverElement = this._dataProvider.prepareHighlightedEntryInfo(entryInd ex);
245 if (popoverElement) {
246 this._entryInfo.appendChild(popoverElement);
247 this._updatePopoverOffset();
248 }
249 }
250
251 _updatePopoverOffset() {
252 var mouseX = this._lastMouseOffsetX;
253 var mouseY = this._lastMouseOffsetY;
254 var parentWidth = this._entryInfo.parentElement.clientWidth;
255 var parentHeight = this._entryInfo.parentElement.clientHeight;
256 var infoWidth = this._entryInfo.clientWidth;
257 var infoHeight = this._entryInfo.clientHeight;
258 var /** @const */ offsetX = 10;
259 var /** @const */ offsetY = 6;
260 var x;
261 var y;
262 for (var quadrant = 0; quadrant < 4; ++quadrant) {
263 var dx = quadrant & 2 ? -offsetX - infoWidth : offsetX;
264 var dy = quadrant & 1 ? -offsetY - infoHeight : offsetY;
265 x = Number.constrain(mouseX + dx, 0, parentWidth - infoWidth);
266 y = Number.constrain(mouseY + dy, 0, parentHeight - infoHeight);
267 if (x >= mouseX || mouseX >= x + infoWidth || y >= mouseY || mouseY >= y + infoHeight)
268 break;
269 }
270 this._entryInfo.style.left = x + 'px';
271 this._entryInfo.style.top = y + 'px';
272 }
273
274 /**
275 * @param {!Event} event
276 */
277 _onClick(event) {
278 this.focus();
279 // onClick comes after dragStart and dragEnd events.
280 // So if there was drag (mouse move) in the middle of that events
281 // we skip the click. Otherwise we jump to the sources.
282 const clickThreshold = 5;
283 if (this.maxDragOffset() > clickThreshold)
284 return;
285 var groupIndex = this._coordinatesToGroupIndex(event.offsetX, event.offsetY) ;
286 if (groupIndex >= 0) {
287 this._toggleGroupVisibility(groupIndex);
288 return;
289 }
290 this.hideRangeSelection();
291 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, this._highlightedEntryIndex);
292 }
293
294 /**
295 * @param {number} groupIndex
296 */
297 _toggleGroupVisibility(groupIndex) {
298 if (!this._isGroupCollapsible(groupIndex))
299 return;
300 var groups = this._rawTimelineData.groups;
301 var group = groups[groupIndex];
302 group.expanded = !group.expanded;
303 this._groupExpansionState[group.name] = group.expanded;
304 if (this._groupExpansionSetting)
305 this._groupExpansionSetting.set(this._groupExpansionState);
306 this._updateLevelPositions();
307
308 this._updateHighlight();
309 if (!group.expanded) {
310 var timelineData = this._timelineData();
311 var level = timelineData.entryLevels[this._selectedEntryIndex];
312 if (this._selectedEntryIndex >= 0 && level >= group.startLevel &&
313 (groupIndex === groups.length || groups[groupIndex + 1].startLevel > l evel))
314 this._selectedEntryIndex = -1;
315 }
316
317 this._updateHeight();
318 this._resetCanvas();
319 this._draw(this._offsetWidth, this._offsetHeight);
320 }
321
322 /**
323 * @param {!Event} e
324 */
325 _onKeyDown(e) {
326 this._handleSelectionNavigation(e);
327 }
328
329 /**
330 * @param {!Event} e
331 */
332 _handleSelectionNavigation(e) {
333 if (!WebInspector.KeyboardShortcut.hasNoModifiers(e))
334 return;
335 if (this._selectedEntryIndex === -1)
336 return;
337 var timelineData = this._timelineData();
338 if (!timelineData)
339 return;
340
341 /**
342 * @param {number} time
343 * @param {number} entryIndex
344 * @return {number}
345 */
346 function timeComparator(time, entryIndex) {
347 return time - timelineData.entryStartTimes[entryIndex];
348 }
349
350 /**
351 * @param {number} entry1
352 * @param {number} entry2
353 * @return {boolean}
354 */
355 function entriesIntersect(entry1, entry2) {
356 var start1 = timelineData.entryStartTimes[entry1];
357 var start2 = timelineData.entryStartTimes[entry2];
358 var end1 = start1 + timelineData.entryTotalTimes[entry1];
359 var end2 = start2 + timelineData.entryTotalTimes[entry2];
360 return start1 < end2 && start2 < end1;
361 }
362
363 var keys = WebInspector.KeyboardShortcut.Keys;
364 if (e.keyCode === keys.Left.code || e.keyCode === keys.Right.code) {
365 var level = timelineData.entryLevels[this._selectedEntryIndex];
366 var levelIndexes = this._timelineLevels[level];
367 var indexOnLevel = levelIndexes.lowerBound(this._selectedEntryIndex);
368 indexOnLevel += e.keyCode === keys.Left.code ? -1 : 1;
369 e.consume(true);
370 if (indexOnLevel >= 0 && indexOnLevel < levelIndexes.length)
371 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelect ed, levelIndexes[indexOnLevel]);
372 return;
373 }
374 if (e.keyCode === keys.Up.code || e.keyCode === keys.Down.code) {
375 e.consume(true);
376 var level = timelineData.entryLevels[this._selectedEntryIndex];
377 level += e.keyCode === keys.Up.code ? -1 : 1;
378 if (level < 0 || level >= this._timelineLevels.length)
379 return;
380 var entryTime = timelineData.entryStartTimes[this._selectedEntryIndex] +
381 timelineData.entryTotalTimes[this._selectedEntryIndex] / 2;
382 var levelIndexes = this._timelineLevels[level];
383 var indexOnLevel = levelIndexes.upperBound(entryTime, timeComparator) - 1;
384 if (!entriesIntersect(this._selectedEntryIndex, levelIndexes[indexOnLevel] )) {
385 ++indexOnLevel;
386 if (indexOnLevel >= levelIndexes.length ||
387 !entriesIntersect(this._selectedEntryIndex, levelIndexes[indexOnLeve l]))
388 return;
389 }
390 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected , levelIndexes[indexOnLevel]);
391 }
392 }
393
394 /**
395 * @param {number} x
396 * @return {number}
397 */
398 _cursorTime(x) {
399 return (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTime + this._minimumBoundary;
400 }
401
402 /**
403 * @param {number} x
404 * @param {number} y
405 * @return {number}
406 */
407 _coordinatesToEntryIndex(x, y) {
408 if (x < 0 || y < 0)
409 return -1;
410 y += this.scrollOffset();
411 var timelineData = this._timelineData();
412 if (!timelineData)
413 return -1;
414 var cursorTime = this._cursorTime(x);
415 var cursorLevel = this._visibleLevelOffsets.upperBound(y) - 1;
416 if (cursorLevel < 0 || !this._visibleLevels[cursorLevel])
417 return -1;
418 var offsetFromLevel = y - this._visibleLevelOffsets[cursorLevel];
419 if (offsetFromLevel > this._barHeight)
420 return -1;
421 var entryStartTimes = timelineData.entryStartTimes;
422 var entryTotalTimes = timelineData.entryTotalTimes;
423 var entryIndexes = this._timelineLevels[cursorLevel];
424 if (!entryIndexes || !entryIndexes.length)
425 return -1;
426
427 /**
428 * @param {number} time
429 * @param {number} entryIndex
430 * @return {number}
431 */
432 function comparator(time, entryIndex) {
433 return time - entryStartTimes[entryIndex];
434 }
435 var indexOnLevel = Math.max(entryIndexes.upperBound(cursorTime, comparator) - 1, 0);
436
437 /**
438 * @this {WebInspector.FlameChart}
439 * @param {number} entryIndex
440 * @return {boolean}
441 */
442 function checkEntryHit(entryIndex) {
443 if (entryIndex === undefined)
444 return false;
445 var startTime = entryStartTimes[entryIndex];
446 var duration = entryTotalTimes[entryIndex];
447 if (isNaN(duration)) {
448 var dx = (startTime - cursorTime) / this._pixelToTime;
449 var dy = this._barHeight / 2 - offsetFromLevel;
450 return dx * dx + dy * dy < this._markerRadius * this._markerRadius;
451 }
452 var endTime = startTime + duration;
453 var barThreshold = 3 * this._pixelToTime;
454 return startTime - barThreshold < cursorTime && cursorTime < endTime + bar Threshold;
455 }
456
457 var entryIndex = entryIndexes[indexOnLevel];
458 if (checkEntryHit.call(this, entryIndex))
459 return entryIndex;
460 entryIndex = entryIndexes[indexOnLevel + 1];
461 if (checkEntryHit.call(this, entryIndex))
462 return entryIndex;
463 return -1;
464 }
465
466 /**
467 * @param {number} x
468 * @param {number} y
469 * @return {number}
470 */
471 _coordinatesToGroupIndex(x, y) {
472 if (x < 0 || y < 0)
473 return -1;
474 y += this.scrollOffset();
475 var groups = this._rawTimelineData.groups || [];
476 var group = this._groupOffsets.upperBound(y) - 1;
477
478 if (group < 0 || group >= groups.length || y - this._groupOffsets[group] >= groups[group].style.height)
479 return -1;
480 var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getCont ext('2d'));
481 context.save();
482 context.font = groups[group].style.font;
483 var right = this._headerLeftPadding + this._labelWidthForGroup(context, grou ps[group]);
484 context.restore();
485 if (x > right)
486 return -1;
487
488 return group;
489 }
490
491 /**
492 * @param {number} x
493 * @return {number}
494 */
495 _markerIndexAtPosition(x) {
496 var markers = this._timelineData().markers;
497 if (!markers)
498 return -1;
499 var accurracyOffsetPx = 1;
500 var time = this._cursorTime(x);
501 var leftTime = this._cursorTime(x - accurracyOffsetPx);
502 var rightTime = this._cursorTime(x + accurracyOffsetPx);
503
504 var left = this._markerIndexBeforeTime(leftTime);
505 var markerIndex = -1;
506 var distance = Infinity;
507 for (var i = left; i < markers.length && markers[i].startTime() < rightTime; i++) {
508 var nextDistance = Math.abs(markers[i].startTime() - time);
509 if (nextDistance < distance) {
510 markerIndex = i;
511 distance = nextDistance;
512 }
513 }
514 return markerIndex;
515 }
516
517 /**
518 * @param {number} time
519 * @return {number}
520 */
521 _markerIndexBeforeTime(time) {
522 return this._timelineData().markers.lowerBound(
523 time, (markerTimestamp, marker) => markerTimestamp - marker.startTime()) ;
524 }
525
526 /**
527 * @param {number} height
528 * @param {number} width
529 */
530 _draw(width, height) {
531 var timelineData = this._timelineData();
532 if (!timelineData)
533 return;
534
535 var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getCont ext('2d'));
536 context.save();
537 var ratio = window.devicePixelRatio;
538 var top = this.scrollOffset();
539 context.scale(ratio, ratio);
540 context.translate(0, -top);
541 var defaultFont = '11px ' + WebInspector.fontFamily();
542 context.font = defaultFont;
543
544 var timeWindowRight = this._timeWindowRight;
545 var timeWindowLeft = this._timeWindowLeft - this._paddingLeft / this._timeTo Pixel;
546 var entryTotalTimes = timelineData.entryTotalTimes;
547 var entryStartTimes = timelineData.entryStartTimes;
548 var entryLevels = timelineData.entryLevels;
549
550 var titleIndices = [];
551 var markerIndices = [];
552 var textPadding = this._dataProvider.textPadding();
553 var minTextWidth = 2 * textPadding + this._measureWidth(context, '\u2026');
554 var barHeight = this._barHeight;
555 var minVisibleBarLevel = Math.max(this._visibleLevelOffsets.upperBound(top) - 1, 0);
556
557 /** @type {!Map<string, !Array<number>>} */
558 var colorBuckets = new Map();
559 for (var level = minVisibleBarLevel; level < this._dataProvider.maxStackDept h(); ++level) {
560 if (this._levelToHeight(level) > top + height)
561 break;
562 if (!this._visibleLevels[level])
563 continue;
564
565 // Entries are ordered by start time within a level, so find the last visi ble entry.
566 var levelIndexes = this._timelineLevels[level];
567 var rightIndexOnLevel =
568 levelIndexes.lowerBound(timeWindowRight, (time, entryIndex) => time - entryStartTimes[entryIndex]) - 1;
569 var lastDrawOffset = Infinity;
570 for (var entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; -- entryIndexOnLevel) {
571 var entryIndex = levelIndexes[entryIndexOnLevel];
572 var entryStartTime = entryStartTimes[entryIndex];
573 var entryOffsetRight = entryStartTime + (entryTotalTimes[entryIndex] || 0);
574 if (entryOffsetRight <= timeWindowLeft)
575 break;
576
577 var barX = this._timeToPositionClipped(entryStartTime);
578 // Check if the entry entirely fits into an already drawn pixel, we can just skip drawing it.
579 if (barX >= lastDrawOffset)
580 continue;
581 lastDrawOffset = barX;
582
583 var color = this._dataProvider.entryColor(entryIndex);
584 var bucket = colorBuckets.get(color);
585 if (!bucket) {
586 bucket = [];
587 colorBuckets.set(color, bucket);
588 }
589 bucket.push(entryIndex);
590 }
591 }
592
593 var colors = colorBuckets.keysArray();
594 // We don't use for-of here because it's slow.
595 for (var c = 0; c < colors.length; ++c) {
596 var color = colors[c];
597 var indexes = colorBuckets.get(color);
598 context.beginPath();
599 context.fillStyle = color;
600 for (var i = 0; i < indexes.length; ++i) {
601 var entryIndex = indexes[i];
602 var entryStartTime = entryStartTimes[entryIndex];
603 var barX = this._timeToPositionClipped(entryStartTime);
604 var duration = entryTotalTimes[entryIndex];
605 var barLevel = entryLevels[entryIndex];
606 var barY = this._levelToHeight(barLevel);
607 if (isNaN(duration)) {
608 context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
609 context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math.PI * 2);
610 markerIndices.push(entryIndex);
611 continue;
612 }
613 var barRight = this._timeToPositionClipped(entryStartTime + duration);
614 var barWidth = Math.max(barRight - barX, 1);
615 context.rect(barX, barY, barWidth - 0.4, barHeight - 1);
616 if (barWidth > minTextWidth || this._dataProvider.forceDecoration(entryI ndex))
617 titleIndices.push(entryIndex);
618 }
619 context.fill();
620 }
621
622 context.strokeStyle = 'rgba(0, 0, 0, 0.2)';
623 context.beginPath();
624 for (var m = 0; m < markerIndices.length; ++m) {
625 var entryIndex = markerIndices[m];
626 var entryStartTime = entryStartTimes[entryIndex];
627 var barX = this._timeToPositionClipped(entryStartTime);
628 var barLevel = entryLevels[entryIndex];
629 var barY = this._levelToHeight(barLevel);
630 context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
631 context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math.PI * 2 );
632 }
633 context.stroke();
634
635 context.textBaseline = 'alphabetic';
636 var textBaseHeight = this._barHeight - this._dataProvider.textBaseline();
637
638 for (var i = 0; i < titleIndices.length; ++i) {
639 var entryIndex = titleIndices[i];
640 var entryStartTime = entryStartTimes[entryIndex];
641 var barX = this._timeToPositionClipped(entryStartTime);
642 var barRight = Math.min(this._timeToPositionClipped(entryStartTime + entry TotalTimes[entryIndex]), width) + 1;
643 var barWidth = barRight - barX;
644 var barLevel = entryLevels[entryIndex];
645 var barY = this._levelToHeight(barLevel);
646 var text = this._dataProvider.entryTitle(entryIndex);
647 if (text && text.length) {
648 context.font = this._dataProvider.entryFont(entryIndex) || defaultFont;
649 text = this._prepareText(context, text, barWidth - 2 * textPadding);
650 }
651 var unclippedBarX = this._timeToPosition(entryStartTime);
652 if (this._dataProvider.decorateEntry(
653 entryIndex, context, text, barX, barY, barWidth, barHeight, unclip pedBarX, this._timeToPixel))
654 continue;
655 if (!text || !text.length)
656 continue;
657 context.fillStyle = this._dataProvider.textColor(entryIndex);
658 context.fillText(text, barX + textPadding, barY + textBaseHeight);
659 }
660
661 this._drawFlowEvents(context, width, height);
662
663 context.restore();
664
665 WebInspector.TimelineGrid.drawCanvasGrid(context, this._calculator, 3);
666 this._drawMarkers();
667 this._drawGroupHeaders(width, height);
668
669 this._updateElementPosition(this._highlightElement, this._highlightedEntryIn dex);
670 this._updateElementPosition(this._selectedElement, this._selectedEntryIndex) ;
671 this._updateMarkerHighlight();
672 }
673
674 /**
675 * @param {number} width
676 * @param {number} height
677 */
678 _drawGroupHeaders(width, height) {
679 var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getCont ext('2d'));
680 var top = this.scrollOffset();
681 var ratio = window.devicePixelRatio;
682 var barHeight = this._barHeight;
683 var textBaseHeight = barHeight - this._dataProvider.textBaseline();
684 var groups = this._rawTimelineData.groups || [];
685 if (!groups.length)
686 return;
687
688 var groupOffsets = this._groupOffsets;
689 var lastGroupOffset = Array.prototype.peekLast.call(groupOffsets);
690 var colorUsage = WebInspector.ThemeSupport.ColorUsage;
691
692 context.save();
693 context.scale(ratio, ratio);
694 context.translate(0, -top);
695
696 context.fillStyle = WebInspector.themeSupport.patchColor('#eee', colorUsage. Background);
697 forEachGroup.call(this, (offset, index, group) => {
698 var paddingHeight = group.style.padding;
699 if (paddingHeight < 5)
700 return;
701 context.fillRect(0, offset - paddingHeight + 2, width, paddingHeight - 4);
702 });
703 if (groups.length && lastGroupOffset < top + height)
704 context.fillRect(0, lastGroupOffset + 2, width, top + height - lastGroupOf fset);
705
706 context.strokeStyle = WebInspector.themeSupport.patchColor('#bbb', colorUsag e.Background);
707 context.beginPath();
708 forEachGroup.call(this, (offset, index, group, isFirst) => {
709 if (isFirst || group.style.padding < 4)
710 return;
711 hLine(offset - 2.5);
712 });
713 hLine(lastGroupOffset + 0.5);
714 context.stroke();
715
716 forEachGroup.call(this, (offset, index, group) => {
717 if (group.style.useFirstLineForOverview)
718 return;
719 if (!this._isGroupCollapsible(index) || group.expanded) {
720 if (!group.style.shareHeaderLine) {
721 context.fillStyle = group.style.backgroundColor;
722 context.fillRect(0, offset, width, group.style.height);
723 }
724 return;
725 }
726 var nextGroup = index + 1;
727 while (nextGroup < groups.length && groups[nextGroup].style.nestingLevel > group.style.nestingLevel)
728 nextGroup++;
729 var endLevel = nextGroup < groups.length ? groups[nextGroup].startLevel : this._dataProvider.maxStackDepth();
730 this._drawCollapsedOverviewForGroup(offset + 1, group.startLevel, endLevel );
731 });
732
733 context.save();
734 forEachGroup.call(this, (offset, index, group) => {
735 context.font = group.style.font;
736 if (this._isGroupCollapsible(index) && !group.expanded || group.style.shar eHeaderLine) {
737 var width = this._labelWidthForGroup(context, group);
738 context.fillStyle = WebInspector.Color.parse(group.style.backgroundColor ).setAlpha(0.7).asString(null);
739 context.fillRect(
740 this._headerLeftPadding - this._headerLabelXPadding, offset + this._ headerLabelYPadding, width,
741 barHeight - 2 * this._headerLabelYPadding);
742 }
743 context.fillStyle = group.style.color;
744 context.fillText(
745 group.name, Math.floor(this._expansionArrowIndent * (group.style.nesti ngLevel + 1) + this._arrowSide),
746 offset + textBaseHeight);
747 });
748 context.restore();
749
750 context.fillStyle = WebInspector.themeSupport.patchColor('#6e6e6e', colorUsa ge.Foreground);
751 context.beginPath();
752 forEachGroup.call(this, (offset, index, group) => {
753 if (this._isGroupCollapsible(index))
754 drawExpansionArrow.call(
755 this, this._expansionArrowIndent * (group.style.nestingLevel + 1),
756 offset + textBaseHeight - this._arrowSide / 2, !!group.expanded);
757 });
758 context.fill();
759
760 context.strokeStyle = WebInspector.themeSupport.patchColor('#ddd', colorUsag e.Background);
761 context.beginPath();
762 context.stroke();
763
764 context.restore();
765
766 /**
767 * @param {number} y
768 */
769 function hLine(y) {
770 context.moveTo(0, y);
771 context.lineTo(width, y);
772 }
773
774 /**
775 * @param {number} x
776 * @param {number} y
777 * @param {boolean} expanded
778 * @this {WebInspector.FlameChart}
779 */
780 function drawExpansionArrow(x, y, expanded) {
781 var arrowHeight = this._arrowSide * Math.sqrt(3) / 2;
782 var arrowCenterOffset = Math.round(arrowHeight / 2);
783 context.save();
784 context.translate(x, y);
785 context.rotate(expanded ? Math.PI / 2 : 0);
786 context.moveTo(-arrowCenterOffset, -this._arrowSide / 2);
787 context.lineTo(-arrowCenterOffset, this._arrowSide / 2);
788 context.lineTo(arrowHeight - arrowCenterOffset, 0);
789 context.restore();
790 }
791
792 /**
793 * @param {function(number, number, !WebInspector.FlameChart.Group, boolean) } callback
794 * @this {WebInspector.FlameChart}
795 */
796 function forEachGroup(callback) {
797 /** @type !Array<{nestingLevel: number, visible: boolean}> */
798 var groupStack = [{nestingLevel: -1, visible: true}];
799 for (var i = 0; i < groups.length; ++i) {
800 var groupTop = groupOffsets[i];
801 var group = groups[i];
802 if (groupTop - group.style.padding > top + height)
803 break;
804 var firstGroup = true;
805 while (groupStack.peekLast().nestingLevel >= group.style.nestingLevel) {
806 groupStack.pop();
807 firstGroup = false;
808 }
809 var parentGroupVisible = groupStack.peekLast().visible;
810 var thisGroupVisible = parentGroupVisible && (!this._isGroupCollapsible( i) || group.expanded);
811 groupStack.push({nestingLevel: group.style.nestingLevel, visible: thisGr oupVisible});
812 if (!parentGroupVisible || groupTop + group.style.height < top)
813 continue;
814 callback(groupTop, i, group, firstGroup);
815 }
816 }
817 }
818
819 /**
820 * @param {!CanvasRenderingContext2D} context
821 * @param {!WebInspector.FlameChart.Group} group
822 * @return {number}
823 */
824 _labelWidthForGroup(context, group) {
825 return this._measureWidth(context, group.name) + this._expansionArrowIndent * (group.style.nestingLevel + 1) +
826 2 * this._headerLabelXPadding;
827 }
828
829 /**
830 * @param {number} y
831 * @param {number} startLevel
832 * @param {number} endLevel
833 */
834 _drawCollapsedOverviewForGroup(y, startLevel, endLevel) {
835 var range = new WebInspector.SegmentedRange(mergeCallback);
836 var timeWindowRight = this._timeWindowRight;
837 var timeWindowLeft = this._timeWindowLeft - this._paddingLeft / this._timeTo Pixel;
838 var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getCont ext('2d'));
839 var barHeight = this._barHeight - 2;
840 var entryStartTimes = this._rawTimelineData.entryStartTimes;
841 var entryTotalTimes = this._rawTimelineData.entryTotalTimes;
842
843 for (var level = startLevel; level < endLevel; ++level) {
844 var levelIndexes = this._timelineLevels[level];
845 var rightIndexOnLevel =
846 levelIndexes.lowerBound(timeWindowRight, (time, entryIndex) => time - entryStartTimes[entryIndex]) - 1;
847 var lastDrawOffset = Infinity;
848
849 for (var entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; -- entryIndexOnLevel) {
850 var entryIndex = levelIndexes[entryIndexOnLevel];
851 var entryStartTime = entryStartTimes[entryIndex];
852 var startPosition = this._timeToPositionClipped(entryStartTime);
853 var entryEndTime = entryStartTime + entryTotalTimes[entryIndex];
854 if (isNaN(entryEndTime) || startPosition >= lastDrawOffset)
855 continue;
856 if (entryEndTime <= timeWindowLeft)
857 break;
858 lastDrawOffset = startPosition;
859 var color = this._dataProvider.entryColor(entryIndex);
860 range.append(new WebInspector.Segment(startPosition, this._timeToPositio nClipped(entryEndTime), color));
861 }
862 }
863
864 var segments = range.segments().slice().sort((a, b) => a.data.localeCompare( b.data));
865 var lastColor;
866 context.beginPath();
867 for (var i = 0; i < segments.length; ++i) {
868 var segment = segments[i];
869 if (lastColor !== segments[i].data) {
870 context.fill();
871 context.beginPath();
872 lastColor = segments[i].data;
873 context.fillStyle = lastColor;
874 }
875 context.rect(segment.begin, y, segment.end - segment.begin, barHeight);
876 }
877 context.fill();
878
879 /**
880 * @param {!WebInspector.Segment} a
881 * @param {!WebInspector.Segment} b
882 * @return {?WebInspector.Segment}
883 */
884 function mergeCallback(a, b) {
885 return a.data === b.data && a.end + 0.4 > b.end ? a : null;
886 }
887 }
888
889 /**
890 * @param {!CanvasRenderingContext2D} context
891 * @param {number} height
892 * @param {number} width
893 */
894 _drawFlowEvents(context, width, height) {
895 var timelineData = this._timelineData();
896 var timeWindowRight = this._timeWindowRight;
897 var timeWindowLeft = this._timeWindowLeft;
898 var flowStartTimes = timelineData.flowStartTimes;
899 var flowEndTimes = timelineData.flowEndTimes;
900 var flowStartLevels = timelineData.flowStartLevels;
901 var flowEndLevels = timelineData.flowEndLevels;
902 var flowCount = flowStartTimes.length;
903 var endIndex = flowStartTimes.lowerBound(timeWindowRight);
904
905 var color = [];
906 var fadeColorsCount = 8;
907 for (var i = 0; i <= fadeColorsCount; ++i)
908 color[i] = 'rgba(128, 0, 0, ' + i / fadeColorsCount + ')';
909 var fadeColorsRange = color.length;
910 var minimumFlowDistancePx = 15;
911 var flowArcHeight = 4 * this._barHeight;
912 var colorIndex = 0;
913 context.lineWidth = 0.5;
914 for (var i = 0; i < endIndex; ++i) {
915 if (flowEndTimes[i] < timeWindowLeft)
916 continue;
917 var startX = this._timeToPosition(flowStartTimes[i]);
918 var endX = this._timeToPosition(flowEndTimes[i]);
919 if (endX - startX < minimumFlowDistancePx)
920 continue;
921 if (startX < -minimumFlowDistancePx && endX > width + minimumFlowDistanceP x)
922 continue;
923 // Assign a trasparent color if the flow is small enough or if the previou s color was a transparent color.
924 if (endX - startX < minimumFlowDistancePx + fadeColorsRange || colorIndex !== color.length - 1) {
925 colorIndex = Math.min(fadeColorsRange - 1, Math.floor(endX - startX - mi nimumFlowDistancePx));
926 context.strokeStyle = color[colorIndex];
927 }
928 var startY = this._levelToHeight(flowStartLevels[i]) + this._barHeight;
929 var endY = this._levelToHeight(flowEndLevels[i]);
930 context.beginPath();
931 context.moveTo(startX, startY);
932 var arcHeight = Math.max(Math.sqrt(Math.abs(startY - endY)), flowArcHeight ) + 5;
933 context.bezierCurveTo(startX, startY + arcHeight, endX, endY + arcHeight, endX, endY + this._barHeight);
934 context.stroke();
935 }
936 }
937
938 _drawMarkers() {
939 var markers = this._timelineData().markers;
940 var left = this._markerIndexBeforeTime(this._calculator.minimumBoundary());
941 var rightBoundary = this._calculator.maximumBoundary();
942
943 var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getCont ext('2d'));
944 context.save();
945 var ratio = window.devicePixelRatio;
946 context.scale(ratio, ratio);
947 var height = WebInspector.FlameChart.DividersBarHeight - 1;
948 for (var i = left; i < markers.length; i++) {
949 var timestamp = markers[i].startTime();
950 if (timestamp > rightBoundary)
951 break;
952 markers[i].draw(context, this._calculator.computePosition(timestamp), heig ht, this._timeToPixel);
953 }
954 context.restore();
955 }
956
957 _updateMarkerHighlight() {
958 var element = this._markerHighlighElement;
959 if (element.parentElement)
960 element.remove();
961 var markerIndex = this._highlightedMarkerIndex;
962 if (markerIndex === -1)
963 return;
964 var marker = this._timelineData().markers[markerIndex];
965 var barX = this._timeToPositionClipped(marker.startTime());
966 element.title = marker.title();
967 var style = element.style;
968 style.left = barX + 'px';
969 style.backgroundColor = marker.color();
970 this.viewportElement.appendChild(element);
971 }
972
973 /**
974 * @param {?WebInspector.FlameChart.TimelineData} timelineData
975 */
976 _processTimelineData(timelineData) {
977 if (!timelineData) {
978 this._timelineLevels = null;
979 this._visibleLevelOffsets = null;
980 this._visibleLevels = null;
981 this._groupOffsets = null;
982 this._rawTimelineData = null;
983 this._rawTimelineDataLength = 0;
984 return;
985 }
986
987 this._rawTimelineData = timelineData;
988 this._rawTimelineDataLength = timelineData.entryStartTimes.length;
989
990 var entryCounters = new Uint32Array(this._dataProvider.maxStackDepth() + 1);
991 for (var i = 0; i < timelineData.entryLevels.length; ++i)
992 ++entryCounters[timelineData.entryLevels[i]];
993 var levelIndexes = new Array(entryCounters.length);
994 for (var i = 0; i < levelIndexes.length; ++i) {
995 levelIndexes[i] = new Uint32Array(entryCounters[i]);
996 entryCounters[i] = 0;
997 }
998 for (var i = 0; i < timelineData.entryLevels.length; ++i) {
999 var level = timelineData.entryLevels[i];
1000 levelIndexes[level][entryCounters[level]++] = i;
1001 }
1002 this._timelineLevels = levelIndexes;
1003 var groups = this._rawTimelineData.groups || [];
1004 for (var i = 0; i < groups.length; ++i) {
1005 var expanded = this._groupExpansionState[groups[i].name];
1006 if (expanded !== undefined)
1007 groups[i].expanded = expanded;
1008 }
1009 this._updateLevelPositions();
1010 this._updateHeight();
1011 }
1012
1013 _updateLevelPositions() {
1014 var levelCount = this._dataProvider.maxStackDepth();
1015 var groups = this._rawTimelineData.groups || [];
1016 this._visibleLevelOffsets = new Uint32Array(levelCount + 1);
1017 this._visibleLevels = new Uint16Array(levelCount);
1018 this._groupOffsets = new Uint32Array(groups.length + 1);
1019
1020 var groupIndex = -1;
1021 var currentOffset = WebInspector.FlameChart.DividersBarHeight;
1022 var visible = true;
1023 /** @type !Array<{nestingLevel: number, visible: boolean}> */
1024 var groupStack = [{nestingLevel: -1, visible: true}];
1025 for (var level = 0; level < levelCount; ++level) {
1026 while (groupIndex < groups.length - 1 && level === groups[groupIndex + 1]. startLevel) {
1027 ++groupIndex;
1028 var style = groups[groupIndex].style;
1029 var nextLevel = true;
1030 while (groupStack.peekLast().nestingLevel >= style.nestingLevel) {
1031 groupStack.pop();
1032 nextLevel = false;
1033 }
1034 var thisGroupIsVisible =
1035 groupIndex >= 0 && this._isGroupCollapsible(groupIndex) ? groups[gro upIndex].expanded : true;
1036 var parentGroupIsVisible = groupStack.peekLast().visible;
1037 visible = thisGroupIsVisible && parentGroupIsVisible;
1038 groupStack.push({nestingLevel: style.nestingLevel, visible: visible});
1039 if (parentGroupIsVisible)
1040 currentOffset += nextLevel ? 0 : style.padding;
1041 this._groupOffsets[groupIndex] = currentOffset;
1042 if (parentGroupIsVisible && !style.shareHeaderLine)
1043 currentOffset += style.height;
1044 }
1045 var isFirstOnLevel = groupIndex >= 0 && level === groups[groupIndex].start Level;
1046 var thisLevelIsVisible = visible || isFirstOnLevel && groups[groupIndex].s tyle.useFirstLineForOverview;
1047 this._visibleLevels[level] = thisLevelIsVisible;
1048 this._visibleLevelOffsets[level] = currentOffset;
1049 if (thisLevelIsVisible || (parentGroupIsVisible && style.shareHeaderLine & & isFirstOnLevel))
1050 currentOffset += this._barHeight;
1051 }
1052 if (groupIndex >= 0)
1053 this._groupOffsets[groupIndex + 1] = currentOffset;
1054 this._visibleLevelOffsets[level] = currentOffset;
1055 }
1056
1057 /**
1058 * @param {number} index
1059 */
1060 _isGroupCollapsible(index) {
1061 var groups = this._rawTimelineData.groups || [];
1062 var style = groups[index].style;
1063 if (!style.shareHeaderLine || !style.collapsible)
1064 return !!style.collapsible;
1065 var isLastGroup = index + 1 >= groups.length;
1066 if (!isLastGroup && groups[index + 1].style.nestingLevel > style.nestingLeve l)
1067 return true;
1068 var nextGroupLevel = isLastGroup ? this._dataProvider.maxStackDepth() : grou ps[index + 1].startLevel;
1069 // For groups that only have one line and share header line, pretend these a re not collapsible.
1070 return nextGroupLevel !== groups[index].startLevel + 1;
1071 }
1072
1073 /**
1074 * @param {number} entryIndex
1075 */
1076 setSelectedEntry(entryIndex) {
1077 if (entryIndex === -1 && !this.isDragging())
1078 this.hideRangeSelection();
1079 if (this._selectedEntryIndex === entryIndex)
1080 return;
1081 this._selectedEntryIndex = entryIndex;
1082 this._revealEntry(entryIndex);
1083 this._updateElementPosition(this._selectedElement, this._selectedEntryIndex) ;
1084 }
1085
1086 /**
1087 * @param {!Element} element
1088 * @param {number} entryIndex
1089 */
1090 _updateElementPosition(element, entryIndex) {
1091 const elementMinWidthPx = 2;
1092 if (element.parentElement)
1093 element.remove();
1094 if (entryIndex === -1)
1095 return;
1096 var timelineData = this._timelineData();
1097 var startTime = timelineData.entryStartTimes[entryIndex];
1098 var endTime = startTime + (timelineData.entryTotalTimes[entryIndex] || 0);
1099 var barX = this._timeToPositionClipped(startTime);
1100 var barRight = this._timeToPositionClipped(endTime);
1101 if (barRight === 0 || barX === this._offsetWidth)
1102 return;
1103 var barWidth = barRight - barX;
1104 var barCenter = barX + barWidth / 2;
1105 barWidth = Math.max(barWidth, elementMinWidthPx);
1106 barX = barCenter - barWidth / 2;
1107 var barY = this._levelToHeight(timelineData.entryLevels[entryIndex]) - this. scrollOffset();
1108 var style = element.style;
1109 style.left = barX + 'px';
1110 style.top = barY + 'px';
1111 style.width = barWidth + 'px';
1112 style.height = this._barHeight - 1 + 'px';
1113 this.viewportElement.appendChild(element);
1114 }
1115
1116 /**
1117 * @param {number} time
1118 * @return {number}
1119 */
1120 _timeToPositionClipped(time) {
1121 return Number.constrain(this._timeToPosition(time), 0, this._offsetWidth);
1122 }
1123
1124 /**
1125 * @param {number} time
1126 * @return {number}
1127 */
1128 _timeToPosition(time) {
1129 return Math.floor((time - this._minimumBoundary) * this._timeToPixel) - this ._pixelWindowLeft + this._paddingLeft;
1130 }
1131
1132 /**
1133 * @param {number} level
1134 * @return {number}
1135 */
1136 _levelToHeight(level) {
1137 return this._visibleLevelOffsets[level];
1138 }
1139
1140 /**
1141 * @param {!CanvasRenderingContext2D} context
1142 * @param {string} text
1143 * @param {number} maxWidth
1144 * @return {string}
1145 */
1146 _prepareText(context, text, maxWidth) {
1147 var /** @const */ maxLength = 200;
1148 if (maxWidth <= 10)
1149 return '';
1150 if (text.length > maxLength)
1151 text = text.trimMiddle(maxLength);
1152 var textWidth = this._measureWidth(context, text);
1153 if (textWidth <= maxWidth)
1154 return text;
1155
1156 var l = 0;
1157 var r = text.length;
1158 var lv = 0;
1159 var rv = textWidth;
1160 while (l < r && lv !== rv && lv !== maxWidth) {
1161 var m = Math.ceil(l + (r - l) * (maxWidth - lv) / (rv - lv));
1162 var mv = this._measureWidth(context, text.trimMiddle(m));
1163 if (mv <= maxWidth) {
1164 l = m;
1165 lv = mv;
1166 } else {
1167 r = m - 1;
1168 rv = mv;
1169 }
1170 }
1171 text = text.trimMiddle(l);
1172 return text !== '\u2026' ? text : '';
1173 }
1174
1175 /**
1176 * @param {!CanvasRenderingContext2D} context
1177 * @param {string} text
1178 * @return {number}
1179 */
1180 _measureWidth(context, text) {
1181 var /** @const */ maxCacheableLength = 200;
1182 if (text.length > maxCacheableLength)
1183 return context.measureText(text).width;
1184
1185 var font = context.font;
1186 var textWidths = this._textWidth.get(font);
1187 if (!textWidths) {
1188 textWidths = new Map();
1189 this._textWidth.set(font, textWidths);
1190 }
1191 var width = textWidths.get(text);
1192 if (!width) {
1193 width = context.measureText(text).width;
1194 textWidths.set(text, width);
1195 }
1196 return width;
1197 }
1198
1199 _updateBoundaries() {
1200 this._totalTime = this._dataProvider.totalTime();
1201 this._minimumBoundary = this._dataProvider.minimumBoundary();
1202
1203 var windowWidth = 1;
1204 if (this._timeWindowRight !== Infinity) {
1205 this._windowLeft = (this._timeWindowLeft - this._minimumBoundary) / this._ totalTime;
1206 this._windowRight = (this._timeWindowRight - this._minimumBoundary) / this ._totalTime;
1207 windowWidth = this._windowRight - this._windowLeft;
1208 } else if (this._timeWindowLeft === Infinity) {
1209 this._windowLeft = Infinity;
1210 this._windowRight = Infinity;
1211 } else {
1212 this._windowLeft = 0;
1213 this._windowRight = 1;
1214 }
1215
1216 var totalPixels = Math.floor((this._offsetWidth - this._paddingLeft) / windo wWidth);
1217 this._pixelWindowLeft = Math.floor(totalPixels * this._windowLeft);
1218
1219 this._timeToPixel = totalPixels / this._totalTime;
1220 this._pixelToTime = this._totalTime / totalPixels;
1221 }
1222
1223 _updateHeight() {
1224 var height = this._levelToHeight(this._dataProvider.maxStackDepth());
1225 this.setContentHeight(height);
1226 }
1227
1228 /**
1229 * @override
1230 */
1231 onResize() {
1232 super.onResize();
1233 this.scheduleUpdate();
1234 }
1235
1236 /**
1237 * @override
1238 */
1239 update() {
1240 if (!this._timelineData())
1241 return;
1242 this._resetCanvas();
1243 this._updateHeight();
1244 this._updateBoundaries();
1245 this._calculator._updateBoundaries(this);
1246 this._draw(this._offsetWidth, this._offsetHeight);
1247 if (!this.isDragging())
1248 this._updateHighlight();
1249 }
1250
1251 /**
1252 * @override
1253 */
1254 reset() {
1255 super.reset();
1256 this._highlightedMarkerIndex = -1;
1257 this._highlightedEntryIndex = -1;
1258 this._selectedEntryIndex = -1;
1259 /** @type {!Map<string,!Map<string,number>>} */
1260 this._textWidth = new Map();
1261 this.update();
1262 }
1263
1264 _enabled() {
1265 return this._rawTimelineDataLength !== 0;
1266 }
112 }; 1267 };
113 1268
114 WebInspector.FlameChart.DividersBarHeight = 18; 1269 WebInspector.FlameChart.DividersBarHeight = 18;
115 1270
116 WebInspector.FlameChart.MinimalTimeWindowMs = 0.5; 1271 WebInspector.FlameChart.MinimalTimeWindowMs = 0.5;
117 1272
118 /** 1273 /**
119 * @interface 1274 * @interface
120 */ 1275 */
121 WebInspector.FlameChartDataProvider = function() 1276 WebInspector.FlameChartDataProvider = function() {};
122 {
123 };
124 1277
125 /** 1278 /**
126 * @typedef {!{name: string, startLevel: number, expanded: (boolean|undefined), style: !WebInspector.FlameChart.GroupStyle}} 1279 * @typedef {!{name: string, startLevel: number, expanded: (boolean|undefined), style: !WebInspector.FlameChart.GroupStyle}}
127 */ 1280 */
128 WebInspector.FlameChart.Group; 1281 WebInspector.FlameChart.Group;
129 1282
130 /** 1283 /**
131 * @typedef {!{ 1284 * @typedef {!{
132 * height: number, 1285 * height: number,
133 * padding: number, 1286 * padding: number,
134 * collapsible: boolean, 1287 * collapsible: boolean,
135 * font: string, 1288 * font: string,
136 * color: string, 1289 * color: string,
137 * backgroundColor: string, 1290 * backgroundColor: string,
138 * nestingLevel: number, 1291 * nestingLevel: number,
139 * shareHeaderLine: (boolean|undefined), 1292 * shareHeaderLine: (boolean|undefined),
140 * useFirstLineForOverview: (boolean|undefined) 1293 * useFirstLineForOverview: (boolean|undefined)
141 * }} 1294 * }}
142 */ 1295 */
143 WebInspector.FlameChart.GroupStyle; 1296 WebInspector.FlameChart.GroupStyle;
144 1297
145 /** 1298 /**
146 * @constructor 1299 * @unrestricted
147 * @param {!Array<number>|!Uint16Array} entryLevels
148 * @param {!Array<number>|!Float32Array} entryTotalTimes
149 * @param {!Array<number>|!Float64Array} entryStartTimes
150 * @param {?Array<!WebInspector.FlameChart.Group>} groups
151 */ 1300 */
152 WebInspector.FlameChart.TimelineData = function(entryLevels, entryTotalTimes, en tryStartTimes, groups) 1301 WebInspector.FlameChart.TimelineData = class {
153 { 1302 /**
1303 * @param {!Array<number>|!Uint16Array} entryLevels
1304 * @param {!Array<number>|!Float32Array} entryTotalTimes
1305 * @param {!Array<number>|!Float64Array} entryStartTimes
1306 * @param {?Array<!WebInspector.FlameChart.Group>} groups
1307 */
1308 constructor(entryLevels, entryTotalTimes, entryStartTimes, groups) {
154 this.entryLevels = entryLevels; 1309 this.entryLevels = entryLevels;
155 this.entryTotalTimes = entryTotalTimes; 1310 this.entryTotalTimes = entryTotalTimes;
156 this.entryStartTimes = entryStartTimes; 1311 this.entryStartTimes = entryStartTimes;
157 this.groups = groups; 1312 this.groups = groups;
158 /** @type {!Array.<!WebInspector.FlameChartMarker>} */ 1313 /** @type {!Array.<!WebInspector.FlameChartMarker>} */
159 this.markers = []; 1314 this.markers = [];
160 this.flowStartTimes = []; 1315 this.flowStartTimes = [];
161 this.flowStartLevels = []; 1316 this.flowStartLevels = [];
162 this.flowEndTimes = []; 1317 this.flowEndTimes = [];
163 this.flowEndLevels = []; 1318 this.flowEndLevels = [];
1319 }
164 }; 1320 };
165 1321
166 WebInspector.FlameChartDataProvider.prototype = { 1322 WebInspector.FlameChartDataProvider.prototype = {
167 /** 1323 /**
168 * @return {number} 1324 * @return {number}
169 */ 1325 */
170 barHeight: function() { }, 1326 barHeight: function() {},
171 1327
172 /** 1328 /**
173 * @return {number} 1329 * @return {number}
174 */ 1330 */
175 minimumBoundary: function() { }, 1331 minimumBoundary: function() {},
176 1332
177 /** 1333 /**
178 * @return {number} 1334 * @return {number}
179 */ 1335 */
180 totalTime: function() { }, 1336 totalTime: function() {},
181 1337
182 /** 1338 /**
183 * @param {number} value 1339 * @param {number} value
184 * @param {number=} precision 1340 * @param {number=} precision
185 * @return {string} 1341 * @return {string}
186 */ 1342 */
187 formatValue: function(value, precision) { }, 1343 formatValue: function(value, precision) {},
188 1344
189 /** 1345 /**
190 * @return {number} 1346 * @return {number}
191 */ 1347 */
192 maxStackDepth: function() { }, 1348 maxStackDepth: function() {},
193 1349
194 /** 1350 /**
195 * @return {?WebInspector.FlameChart.TimelineData} 1351 * @return {?WebInspector.FlameChart.TimelineData}
196 */ 1352 */
197 timelineData: function() { }, 1353 timelineData: function() {},
198 1354
199 /** 1355 /**
200 * @param {number} entryIndex 1356 * @param {number} entryIndex
201 * @return {?Element} 1357 * @return {?Element}
202 */ 1358 */
203 prepareHighlightedEntryInfo: function(entryIndex) { }, 1359 prepareHighlightedEntryInfo: function(entryIndex) {},
204 1360
205 /** 1361 /**
206 * @param {number} entryIndex 1362 * @param {number} entryIndex
207 * @return {boolean} 1363 * @return {boolean}
208 */ 1364 */
209 canJumpToEntry: function(entryIndex) { }, 1365 canJumpToEntry: function(entryIndex) {},
210 1366
211 /** 1367 /**
212 * @param {number} entryIndex 1368 * @param {number} entryIndex
213 * @return {?string} 1369 * @return {?string}
214 */ 1370 */
215 entryTitle: function(entryIndex) { }, 1371 entryTitle: function(entryIndex) {},
216 1372
217 /** 1373 /**
218 * @param {number} entryIndex 1374 * @param {number} entryIndex
219 * @return {?string} 1375 * @return {?string}
220 */ 1376 */
221 entryFont: function(entryIndex) { }, 1377 entryFont: function(entryIndex) {},
222 1378
223 /** 1379 /**
224 * @param {number} entryIndex 1380 * @param {number} entryIndex
225 * @return {string} 1381 * @return {string}
226 */ 1382 */
227 entryColor: function(entryIndex) { }, 1383 entryColor: function(entryIndex) {},
228 1384
229 /** 1385 /**
230 * @param {number} entryIndex 1386 * @param {number} entryIndex
231 * @param {!CanvasRenderingContext2D} context 1387 * @param {!CanvasRenderingContext2D} context
232 * @param {?string} text 1388 * @param {?string} text
233 * @param {number} barX 1389 * @param {number} barX
234 * @param {number} barY 1390 * @param {number} barY
235 * @param {number} barWidth 1391 * @param {number} barWidth
236 * @param {number} barHeight 1392 * @param {number} barHeight
237 * @param {number} unclippedBarX 1393 * @param {number} unclippedBarX
238 * @param {number} timeToPixels 1394 * @param {number} timeToPixels
239 * @return {boolean} 1395 * @return {boolean}
240 */ 1396 */
241 decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, bar Height, unclippedBarX, timeToPixels) { }, 1397 decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHe ight, unclippedBarX, timeToPixels) {},
242 1398
243 /** 1399 /**
244 * @param {number} entryIndex 1400 * @param {number} entryIndex
245 * @return {boolean} 1401 * @return {boolean}
246 */ 1402 */
247 forceDecoration: function(entryIndex) { }, 1403 forceDecoration: function(entryIndex) {},
248 1404
249 /** 1405 /**
250 * @param {number} entryIndex 1406 * @param {number} entryIndex
251 * @return {string} 1407 * @return {string}
252 */ 1408 */
253 textColor: function(entryIndex) { }, 1409 textColor: function(entryIndex) {},
254 1410
255 /** 1411 /**
256 * @return {number} 1412 * @return {number}
257 */ 1413 */
258 textBaseline: function() { }, 1414 textBaseline: function() {},
259 1415
260 /** 1416 /**
261 * @return {number} 1417 * @return {number}
262 */ 1418 */
263 textPadding: function() { }, 1419 textPadding: function() {},
264 1420
265 /** 1421 /**
266 * @return {number} 1422 * @return {number}
267 */ 1423 */
268 paddingLeft: function() { }, 1424 paddingLeft: function() {},
269 }; 1425 };
270 1426
271 /** 1427 /**
272 * @interface 1428 * @interface
273 */ 1429 */
274 WebInspector.FlameChartMarker = function() 1430 WebInspector.FlameChartMarker = function() {};
275 {
276 };
277 1431
278 WebInspector.FlameChartMarker.prototype = { 1432 WebInspector.FlameChartMarker.prototype = {
279 /** 1433 /**
280 * @return {number} 1434 * @return {number}
281 */ 1435 */
282 startTime: function() { }, 1436 startTime: function() {},
283 1437
284 /** 1438 /**
285 * @return {string} 1439 * @return {string}
286 */ 1440 */
287 color: function() { }, 1441 color: function() {},
288 1442
289 /** 1443 /**
290 * @return {string} 1444 * @return {string}
291 */ 1445 */
292 title: function() { }, 1446 title: function() {},
293 1447
294 /** 1448 /**
295 * @param {!CanvasRenderingContext2D} context 1449 * @param {!CanvasRenderingContext2D} context
296 * @param {number} x 1450 * @param {number} x
297 * @param {number} height 1451 * @param {number} height
298 * @param {number} pixelsPerMillisecond 1452 * @param {number} pixelsPerMillisecond
299 */ 1453 */
300 draw: function(context, x, height, pixelsPerMillisecond) { }, 1454 draw: function(context, x, height, pixelsPerMillisecond) {},
301 }; 1455 };
302 1456
303 /** @enum {symbol} */ 1457 /** @enum {symbol} */
304 WebInspector.FlameChart.Events = { 1458 WebInspector.FlameChart.Events = {
305 EntrySelected: Symbol("EntrySelected") 1459 EntrySelected: Symbol('EntrySelected')
306 }; 1460 };
307
308 1461
309 /** 1462 /**
310 * @constructor 1463 * @unrestricted
311 * @param {!{min: number, max: number}|number=} hueSpace
312 * @param {!{min: number, max: number, count: (number|undefined)}|number=} satSp ace
313 * @param {!{min: number, max: number, count: (number|undefined)}|number=} light nessSpace
314 * @param {!{min: number, max: number, count: (number|undefined)}|number=} alpha Space
315 */ 1464 */
316 WebInspector.FlameChart.ColorGenerator = function(hueSpace, satSpace, lightnessS pace, alphaSpace) 1465 WebInspector.FlameChart.ColorGenerator = class {
317 { 1466 /**
318 this._hueSpace = hueSpace || { min: 0, max: 360 }; 1467 * @param {!{min: number, max: number}|number=} hueSpace
1468 * @param {!{min: number, max: number, count: (number|undefined)}|number=} sat Space
1469 * @param {!{min: number, max: number, count: (number|undefined)}|number=} lig htnessSpace
1470 * @param {!{min: number, max: number, count: (number|undefined)}|number=} alp haSpace
1471 */
1472 constructor(hueSpace, satSpace, lightnessSpace, alphaSpace) {
1473 this._hueSpace = hueSpace || {min: 0, max: 360};
319 this._satSpace = satSpace || 67; 1474 this._satSpace = satSpace || 67;
320 this._lightnessSpace = lightnessSpace || 80; 1475 this._lightnessSpace = lightnessSpace || 80;
321 this._alphaSpace = alphaSpace || 1; 1476 this._alphaSpace = alphaSpace || 1;
322 /** @type {!Map<string, string>} */ 1477 /** @type {!Map<string, string>} */
323 this._colors = new Map(); 1478 this._colors = new Map();
324 }; 1479 }
325 1480
326 WebInspector.FlameChart.ColorGenerator.prototype = { 1481 /**
327 /** 1482 * @param {string} id
328 * @param {string} id 1483 * @param {string} color
329 * @param {string} color 1484 */
330 */ 1485 setColorForID(id, color) {
331 setColorForID: function(id, color) 1486 this._colors.set(id, color);
332 { 1487 }
333 this._colors.set(id, color); 1488
334 }, 1489 /**
335 1490 * @param {string} id
336 /** 1491 * @return {string}
337 * @param {string} id 1492 */
338 * @return {string} 1493 colorForID(id) {
339 */ 1494 var color = this._colors.get(id);
340 colorForID: function(id) 1495 if (!color) {
341 { 1496 color = this._generateColorForID(id);
342 var color = this._colors.get(id); 1497 this._colors.set(id, color);
343 if (!color) {
344 color = this._generateColorForID(id);
345 this._colors.set(id, color);
346 }
347 return color;
348 },
349
350 /**
351 * @param {string} id
352 * @return {string}
353 */
354 _generateColorForID: function(id)
355 {
356 var hash = String.hashCode(id);
357 var h = this._indexToValueInSpace(hash, this._hueSpace);
358 var s = this._indexToValueInSpace(hash >> 8, this._satSpace);
359 var l = this._indexToValueInSpace(hash >> 16, this._lightnessSpace);
360 var a = this._indexToValueInSpace(hash >> 24, this._alphaSpace);
361 return "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")";
362 },
363
364 /**
365 * @param {number} index
366 * @param {!{min: number, max: number, count: (number|undefined)}|number} sp ace
367 * @return {number}
368 */
369 _indexToValueInSpace: function(index, space)
370 {
371 if (typeof space === "number")
372 return space;
373 var count = space.count || space.max - space.min;
374 index %= count;
375 return space.min + Math.floor(index / (count - 1) * (space.max - space.m in));
376 } 1498 }
377 }; 1499 return color;
378 1500 }
1501
1502 /**
1503 * @param {string} id
1504 * @return {string}
1505 */
1506 _generateColorForID(id) {
1507 var hash = String.hashCode(id);
1508 var h = this._indexToValueInSpace(hash, this._hueSpace);
1509 var s = this._indexToValueInSpace(hash >> 8, this._satSpace);
1510 var l = this._indexToValueInSpace(hash >> 16, this._lightnessSpace);
1511 var a = this._indexToValueInSpace(hash >> 24, this._alphaSpace);
1512 return 'hsla(' + h + ', ' + s + '%, ' + l + '%, ' + a + ')';
1513 }
1514
1515 /**
1516 * @param {number} index
1517 * @param {!{min: number, max: number, count: (number|undefined)}|number} spac e
1518 * @return {number}
1519 */
1520 _indexToValueInSpace(index, space) {
1521 if (typeof space === 'number')
1522 return space;
1523 var count = space.count || space.max - space.min;
1524 index %= count;
1525 return space.min + Math.floor(index / (count - 1) * (space.max - space.min)) ;
1526 }
1527 };
379 1528
380 /** 1529 /**
381 * @constructor
382 * @implements {WebInspector.TimelineGrid.Calculator} 1530 * @implements {WebInspector.TimelineGrid.Calculator}
383 * @param {!WebInspector.FlameChartDataProvider} dataProvider 1531 * @unrestricted
384 */ 1532 */
385 WebInspector.FlameChart.Calculator = function(dataProvider) 1533 WebInspector.FlameChart.Calculator = class {
386 { 1534 /**
1535 * @param {!WebInspector.FlameChartDataProvider} dataProvider
1536 */
1537 constructor(dataProvider) {
387 this._dataProvider = dataProvider; 1538 this._dataProvider = dataProvider;
388 this._paddingLeft = 0; 1539 this._paddingLeft = 0;
389 }; 1540 }
390 1541
391 WebInspector.FlameChart.Calculator.prototype = { 1542 /**
392 /** 1543 * @override
393 * @override 1544 * @return {number}
394 * @return {number} 1545 */
395 */ 1546 paddingLeft() {
396 paddingLeft: function() 1547 return this._paddingLeft;
397 { 1548 }
398 return this._paddingLeft; 1549
399 }, 1550 /**
400 1551 * @param {!WebInspector.FlameChart} mainPane
401 /** 1552 */
402 * @param {!WebInspector.FlameChart} mainPane 1553 _updateBoundaries(mainPane) {
403 */ 1554 this._totalTime = mainPane._dataProvider.totalTime();
404 _updateBoundaries: function(mainPane) 1555 this._zeroTime = mainPane._dataProvider.minimumBoundary();
405 { 1556 this._minimumBoundaries = this._zeroTime + mainPane._windowLeft * this._tota lTime;
406 this._totalTime = mainPane._dataProvider.totalTime(); 1557 this._maximumBoundaries = this._zeroTime + mainPane._windowRight * this._tot alTime;
407 this._zeroTime = mainPane._dataProvider.minimumBoundary(); 1558 this._paddingLeft = mainPane._paddingLeft;
408 this._minimumBoundaries = this._zeroTime + mainPane._windowLeft * this._ totalTime; 1559 this._width = mainPane._offsetWidth - this._paddingLeft;
409 this._maximumBoundaries = this._zeroTime + mainPane._windowRight * this. _totalTime; 1560 this._timeToPixel = this._width / this.boundarySpan();
410 this._paddingLeft = mainPane._paddingLeft; 1561 }
411 this._width = mainPane._offsetWidth - this._paddingLeft; 1562
412 this._timeToPixel = this._width / this.boundarySpan(); 1563 /**
413 }, 1564 * @override
414 1565 * @param {number} time
415 /** 1566 * @return {number}
416 * @override 1567 */
417 * @param {number} time 1568 computePosition(time) {
418 * @return {number} 1569 return Math.round((time - this._minimumBoundaries) * this._timeToPixel + thi s._paddingLeft);
419 */ 1570 }
420 computePosition: function(time) 1571
421 { 1572 /**
422 return Math.round((time - this._minimumBoundaries) * this._timeToPixel + this._paddingLeft); 1573 * @override
423 }, 1574 * @param {number} value
424 1575 * @param {number=} precision
425 /** 1576 * @return {string}
426 * @override 1577 */
427 * @param {number} value 1578 formatValue(value, precision) {
428 * @param {number=} precision 1579 return this._dataProvider.formatValue(value - this._zeroTime, precision);
429 * @return {string} 1580 }
430 */ 1581
431 formatValue: function(value, precision) 1582 /**
432 { 1583 * @override
433 return this._dataProvider.formatValue(value - this._zeroTime, precision) ; 1584 * @return {number}
434 }, 1585 */
435 1586 maximumBoundary() {
436 /** 1587 return this._maximumBoundaries;
437 * @override 1588 }
438 * @return {number} 1589
439 */ 1590 /**
440 maximumBoundary: function() 1591 * @override
441 { 1592 * @return {number}
442 return this._maximumBoundaries; 1593 */
443 }, 1594 minimumBoundary() {
444 1595 return this._minimumBoundaries;
445 /** 1596 }
446 * @override 1597
447 * @return {number} 1598 /**
448 */ 1599 * @override
449 minimumBoundary: function() 1600 * @return {number}
450 { 1601 */
451 return this._minimumBoundaries; 1602 zeroTime() {
452 }, 1603 return this._zeroTime;
453 1604 }
454 /** 1605
455 * @override 1606 /**
456 * @return {number} 1607 * @override
457 */ 1608 * @return {number}
458 zeroTime: function() 1609 */
459 { 1610 boundarySpan() {
460 return this._zeroTime; 1611 return this._maximumBoundaries - this._minimumBoundaries;
461 }, 1612 }
462 1613 };
463 /**
464 * @override
465 * @return {number}
466 */
467 boundarySpan: function()
468 {
469 return this._maximumBoundaries - this._minimumBoundaries;
470 }
471 };
472
473 WebInspector.FlameChart.prototype = {
474 /**
475 * @override
476 */
477 willHide: function()
478 {
479 this.hideHighlight();
480 },
481
482 /**
483 * @param {number} entryIndex
484 */
485 highlightEntry: function(entryIndex)
486 {
487 if (this._highlightedEntryIndex === entryIndex)
488 return;
489 this._highlightedEntryIndex = entryIndex;
490 this._updateElementPosition(this._highlightElement, this._highlightedEnt ryIndex);
491 },
492
493 hideHighlight: function()
494 {
495 this._entryInfo.removeChildren();
496 this._highlightedEntryIndex = -1;
497 this._updateElementPosition(this._highlightElement, this._highlightedEnt ryIndex);
498 },
499
500 _resetCanvas: function()
501 {
502 var ratio = window.devicePixelRatio;
503 this._canvas.width = this._offsetWidth * ratio;
504 this._canvas.height = this._offsetHeight * ratio;
505 this._canvas.style.width = this._offsetWidth + "px";
506 this._canvas.style.height = this._offsetHeight + "px";
507 },
508
509 /**
510 * @return {?WebInspector.FlameChart.TimelineData}
511 */
512 _timelineData: function()
513 {
514 if (!this._dataProvider)
515 return null;
516 var timelineData = this._dataProvider.timelineData();
517 if (timelineData !== this._rawTimelineData || timelineData.entryStartTim es.length !== this._rawTimelineDataLength)
518 this._processTimelineData(timelineData);
519 return this._rawTimelineData;
520 },
521
522 /**
523 * @param {number} entryIndex
524 */
525 _revealEntry: function(entryIndex)
526 {
527 var timelineData = this._timelineData();
528 if (!timelineData)
529 return;
530 // Think in terms of not where we are, but where we'll be after animatio n (if present)
531 var timeLeft = this._cancelWindowTimesAnimation ? this._pendingAnimation TimeLeft : this._timeWindowLeft;
532 var timeRight = this._cancelWindowTimesAnimation ? this._pendingAnimatio nTimeRight : this._timeWindowRight;
533 var entryStartTime = timelineData.entryStartTimes[entryIndex];
534 var entryTotalTime = timelineData.entryTotalTimes[entryIndex];
535 var entryEndTime = entryStartTime + entryTotalTime;
536 var minEntryTimeWindow = Math.min(entryTotalTime, timeRight - timeLeft);
537
538 var y = this._levelToHeight(timelineData.entryLevels[entryIndex]);
539 this.setScrollOffset(y, this._barHeight);
540
541 if (timeLeft > entryEndTime) {
542 var delta = timeLeft - entryEndTime + minEntryTimeWindow;
543 this._flameChartDelegate.requestWindowTimes(timeLeft - delta, timeRi ght - delta);
544 } else if (timeRight < entryStartTime) {
545 var delta = entryStartTime - timeRight + minEntryTimeWindow;
546 this._flameChartDelegate.requestWindowTimes(timeLeft + delta, timeRi ght + delta);
547 }
548 },
549
550 /**
551 * @override
552 * @param {number} startTime
553 * @param {number} endTime
554 */
555 setWindowTimes: function(startTime, endTime)
556 {
557 WebInspector.FlameChart.prototype.__proto__.setWindowTimes.call(this, st artTime, endTime);
558 this._updateHighlight();
559 },
560
561 /**
562 * @param {!Event} event
563 */
564 _onMouseMove: function(event)
565 {
566 this._lastMouseOffsetX = event.offsetX;
567 this._lastMouseOffsetY = event.offsetY;
568 if (!this._enabled())
569 return;
570 if (this.isDragging())
571 return;
572 if (this._coordinatesToGroupIndex(event.offsetX, event.offsetY) >= 0) {
573 this.hideHighlight();
574 this.viewportElement.style.cursor = "pointer";
575 return;
576 }
577 this._updateHighlight();
578 },
579
580 _updateHighlight: function()
581 {
582 var inDividersBar = this._lastMouseOffsetY < WebInspector.FlameChart.Div idersBarHeight;
583 this._highlightedMarkerIndex = inDividersBar ? this._markerIndexAtPositi on(this._lastMouseOffsetX) : -1;
584 this._updateMarkerHighlight();
585
586 var entryIndex = this._coordinatesToEntryIndex(this._lastMouseOffsetX, t his._lastMouseOffsetY);
587 if (entryIndex === -1) {
588 this.hideHighlight();
589 return;
590 }
591 if (this.isDragging())
592 return;
593 this._updatePopover(entryIndex);
594 this.viewportElement.style.cursor = this._dataProvider.canJumpToEntry(en tryIndex) ? "pointer" : "default";
595 this.highlightEntry(entryIndex);
596 },
597
598 _onMouseOut: function()
599 {
600 this._lastMouseOffsetX = -1;
601 this._lastMouseOffsetY = -1;
602 this.hideHighlight();
603 },
604
605 /**
606 * @param {number} entryIndex
607 */
608 _updatePopover: function(entryIndex)
609 {
610 if (entryIndex === this._highlightedEntryIndex) {
611 this._updatePopoverOffset();
612 return;
613 }
614 this._entryInfo.removeChildren();
615 var popoverElement = this._dataProvider.prepareHighlightedEntryInfo(entr yIndex);
616 if (popoverElement) {
617 this._entryInfo.appendChild(popoverElement);
618 this._updatePopoverOffset();
619 }
620 },
621
622 _updatePopoverOffset: function()
623 {
624 var mouseX = this._lastMouseOffsetX;
625 var mouseY = this._lastMouseOffsetY;
626 var parentWidth = this._entryInfo.parentElement.clientWidth;
627 var parentHeight = this._entryInfo.parentElement.clientHeight;
628 var infoWidth = this._entryInfo.clientWidth;
629 var infoHeight = this._entryInfo.clientHeight;
630 var /** @const */ offsetX = 10;
631 var /** @const */ offsetY = 6;
632 var x;
633 var y;
634 for (var quadrant = 0; quadrant < 4; ++quadrant) {
635 var dx = quadrant & 2 ? -offsetX - infoWidth : offsetX;
636 var dy = quadrant & 1 ? -offsetY - infoHeight : offsetY;
637 x = Number.constrain(mouseX + dx, 0, parentWidth - infoWidth);
638 y = Number.constrain(mouseY + dy, 0, parentHeight - infoHeight);
639 if (x >= mouseX || mouseX >= x + infoWidth || y >= mouseY || mouseY >= y + infoHeight)
640 break;
641 }
642 this._entryInfo.style.left = x + "px";
643 this._entryInfo.style.top = y + "px";
644 },
645
646 /**
647 * @param {!Event} event
648 */
649 _onClick: function(event)
650 {
651 this.focus();
652 // onClick comes after dragStart and dragEnd events.
653 // So if there was drag (mouse move) in the middle of that events
654 // we skip the click. Otherwise we jump to the sources.
655 const clickThreshold = 5;
656 if (this.maxDragOffset() > clickThreshold)
657 return;
658 var groupIndex = this._coordinatesToGroupIndex(event.offsetX, event.offs etY);
659 if (groupIndex >= 0) {
660 this._toggleGroupVisibility(groupIndex);
661 return;
662 }
663 this.hideRangeSelection();
664 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelect ed, this._highlightedEntryIndex);
665 },
666
667 /**
668 * @param {number} groupIndex
669 */
670 _toggleGroupVisibility: function(groupIndex)
671 {
672 if (!this._isGroupCollapsible(groupIndex))
673 return;
674 var groups = this._rawTimelineData.groups;
675 var group = groups[groupIndex];
676 group.expanded = !group.expanded;
677 this._groupExpansionState[group.name] = group.expanded;
678 if (this._groupExpansionSetting)
679 this._groupExpansionSetting.set(this._groupExpansionState);
680 this._updateLevelPositions();
681
682 this._updateHighlight();
683 if (!group.expanded) {
684 var timelineData = this._timelineData();
685 var level = timelineData.entryLevels[this._selectedEntryIndex];
686 if (this._selectedEntryIndex >= 0 && level >= group.startLevel && (g roupIndex === groups.length || groups[groupIndex + 1].startLevel > level))
687 this._selectedEntryIndex = -1;
688 }
689
690 this._updateHeight();
691 this._resetCanvas();
692 this._draw(this._offsetWidth, this._offsetHeight);
693 },
694
695 /**
696 * @param {!Event} e
697 */
698 _onKeyDown: function(e)
699 {
700 this._handleSelectionNavigation(e);
701 },
702
703 /**
704 * @param {!Event} e
705 */
706 _handleSelectionNavigation: function(e)
707 {
708 if (!WebInspector.KeyboardShortcut.hasNoModifiers(e))
709 return;
710 if (this._selectedEntryIndex === -1)
711 return;
712 var timelineData = this._timelineData();
713 if (!timelineData)
714 return;
715
716 /**
717 * @param {number} time
718 * @param {number} entryIndex
719 * @return {number}
720 */
721 function timeComparator(time, entryIndex)
722 {
723 return time - timelineData.entryStartTimes[entryIndex];
724 }
725
726 /**
727 * @param {number} entry1
728 * @param {number} entry2
729 * @return {boolean}
730 */
731 function entriesIntersect(entry1, entry2)
732 {
733 var start1 = timelineData.entryStartTimes[entry1];
734 var start2 = timelineData.entryStartTimes[entry2];
735 var end1 = start1 + timelineData.entryTotalTimes[entry1];
736 var end2 = start2 + timelineData.entryTotalTimes[entry2];
737 return start1 < end2 && start2 < end1;
738 }
739
740 var keys = WebInspector.KeyboardShortcut.Keys;
741 if (e.keyCode === keys.Left.code || e.keyCode === keys.Right.code) {
742 var level = timelineData.entryLevels[this._selectedEntryIndex];
743 var levelIndexes = this._timelineLevels[level];
744 var indexOnLevel = levelIndexes.lowerBound(this._selectedEntryIndex) ;
745 indexOnLevel += e.keyCode === keys.Left.code ? -1 : 1;
746 e.consume(true);
747 if (indexOnLevel >= 0 && indexOnLevel < levelIndexes.length)
748 this.dispatchEventToListeners(WebInspector.FlameChart.Events.Ent rySelected, levelIndexes[indexOnLevel]);
749 return;
750 }
751 if (e.keyCode === keys.Up.code || e.keyCode === keys.Down.code) {
752 e.consume(true);
753 var level = timelineData.entryLevels[this._selectedEntryIndex];
754 level += e.keyCode === keys.Up.code ? -1 : 1;
755 if (level < 0 || level >= this._timelineLevels.length)
756 return;
757 var entryTime = timelineData.entryStartTimes[this._selectedEntryInde x] + timelineData.entryTotalTimes[this._selectedEntryIndex] / 2;
758 var levelIndexes = this._timelineLevels[level];
759 var indexOnLevel = levelIndexes.upperBound(entryTime, timeComparator ) - 1;
760 if (!entriesIntersect(this._selectedEntryIndex, levelIndexes[indexOn Level])) {
761 ++indexOnLevel;
762 if (indexOnLevel >= levelIndexes.length || !entriesIntersect(thi s._selectedEntryIndex, levelIndexes[indexOnLevel]))
763 return;
764 }
765 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySe lected, levelIndexes[indexOnLevel]);
766 }
767 },
768
769 /**
770 * @param {number} x
771 * @return {number}
772 */
773 _cursorTime: function(x)
774 {
775 return (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTi me + this._minimumBoundary;
776 },
777
778 /**
779 * @param {number} x
780 * @param {number} y
781 * @return {number}
782 */
783 _coordinatesToEntryIndex: function(x, y)
784 {
785 if (x < 0 || y < 0)
786 return -1;
787 y += this.scrollOffset();
788 var timelineData = this._timelineData();
789 if (!timelineData)
790 return -1;
791 var cursorTime = this._cursorTime(x);
792 var cursorLevel = this._visibleLevelOffsets.upperBound(y) - 1;
793 if (cursorLevel < 0 || !this._visibleLevels[cursorLevel])
794 return -1;
795 var offsetFromLevel = y - this._visibleLevelOffsets[cursorLevel];
796 if (offsetFromLevel > this._barHeight)
797 return -1;
798 var entryStartTimes = timelineData.entryStartTimes;
799 var entryTotalTimes = timelineData.entryTotalTimes;
800 var entryIndexes = this._timelineLevels[cursorLevel];
801 if (!entryIndexes || !entryIndexes.length)
802 return -1;
803
804 /**
805 * @param {number} time
806 * @param {number} entryIndex
807 * @return {number}
808 */
809 function comparator(time, entryIndex)
810 {
811 return time - entryStartTimes[entryIndex];
812 }
813 var indexOnLevel = Math.max(entryIndexes.upperBound(cursorTime, comparat or) - 1, 0);
814
815 /**
816 * @this {WebInspector.FlameChart}
817 * @param {number} entryIndex
818 * @return {boolean}
819 */
820 function checkEntryHit(entryIndex)
821 {
822 if (entryIndex === undefined)
823 return false;
824 var startTime = entryStartTimes[entryIndex];
825 var duration = entryTotalTimes[entryIndex];
826 if (isNaN(duration)) {
827 var dx = (startTime - cursorTime) / this._pixelToTime;
828 var dy = this._barHeight / 2 - offsetFromLevel;
829 return dx * dx + dy * dy < this._markerRadius * this._markerRadi us;
830 }
831 var endTime = startTime + duration;
832 var barThreshold = 3 * this._pixelToTime;
833 return startTime - barThreshold < cursorTime && cursorTime < endTime + barThreshold;
834 }
835
836 var entryIndex = entryIndexes[indexOnLevel];
837 if (checkEntryHit.call(this, entryIndex))
838 return entryIndex;
839 entryIndex = entryIndexes[indexOnLevel + 1];
840 if (checkEntryHit.call(this, entryIndex))
841 return entryIndex;
842 return -1;
843 },
844
845 /**
846 * @param {number} x
847 * @param {number} y
848 * @return {number}
849 */
850 _coordinatesToGroupIndex: function(x, y)
851 {
852 if (x < 0 || y < 0)
853 return -1;
854 y += this.scrollOffset();
855 var groups = this._rawTimelineData.groups || [];
856 var group = this._groupOffsets.upperBound(y) - 1;
857
858 if (group < 0 || group >= groups.length || y - this._groupOffsets[group] >= groups[group].style.height)
859 return -1;
860 var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.get Context("2d"));
861 context.save();
862 context.font = groups[group].style.font;
863 var right = this._headerLeftPadding + this._labelWidthForGroup(context, groups[group]);
864 context.restore();
865 if (x > right)
866 return -1;
867
868 return group;
869 },
870
871 /**
872 * @param {number} x
873 * @return {number}
874 */
875 _markerIndexAtPosition: function(x)
876 {
877 var markers = this._timelineData().markers;
878 if (!markers)
879 return -1;
880 var accurracyOffsetPx = 1;
881 var time = this._cursorTime(x);
882 var leftTime = this._cursorTime(x - accurracyOffsetPx);
883 var rightTime = this._cursorTime(x + accurracyOffsetPx);
884
885 var left = this._markerIndexBeforeTime(leftTime);
886 var markerIndex = -1;
887 var distance = Infinity;
888 for (var i = left; i < markers.length && markers[i].startTime() < rightT ime; i++) {
889 var nextDistance = Math.abs(markers[i].startTime() - time);
890 if (nextDistance < distance) {
891 markerIndex = i;
892 distance = nextDistance;
893 }
894 }
895 return markerIndex;
896 },
897
898 /**
899 * @param {number} time
900 * @return {number}
901 */
902 _markerIndexBeforeTime: function(time)
903 {
904 return this._timelineData().markers.lowerBound(time, (markerTimestamp, m arker) => markerTimestamp - marker.startTime());
905 },
906
907 /**
908 * @param {number} height
909 * @param {number} width
910 */
911 _draw: function(width, height)
912 {
913 var timelineData = this._timelineData();
914 if (!timelineData)
915 return;
916
917 var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.get Context("2d"));
918 context.save();
919 var ratio = window.devicePixelRatio;
920 var top = this.scrollOffset();
921 context.scale(ratio, ratio);
922 context.translate(0, -top);
923 var defaultFont = "11px " + WebInspector.fontFamily();
924 context.font = defaultFont;
925
926 var timeWindowRight = this._timeWindowRight;
927 var timeWindowLeft = this._timeWindowLeft - this._paddingLeft / this._ti meToPixel;
928 var entryTotalTimes = timelineData.entryTotalTimes;
929 var entryStartTimes = timelineData.entryStartTimes;
930 var entryLevels = timelineData.entryLevels;
931
932 var titleIndices = [];
933 var markerIndices = [];
934 var textPadding = this._dataProvider.textPadding();
935 var minTextWidth = 2 * textPadding + this._measureWidth(context, "\u2026 ");
936 var barHeight = this._barHeight;
937 var minVisibleBarLevel = Math.max(this._visibleLevelOffsets.upperBound(t op) - 1, 0);
938
939 /** @type {!Map<string, !Array<number>>} */
940 var colorBuckets = new Map();
941 for (var level = minVisibleBarLevel; level < this._dataProvider.maxStack Depth(); ++level) {
942 if (this._levelToHeight(level) > top + height)
943 break;
944 if (!this._visibleLevels[level])
945 continue;
946
947 // Entries are ordered by start time within a level, so find the las t visible entry.
948 var levelIndexes = this._timelineLevels[level];
949 var rightIndexOnLevel = levelIndexes.lowerBound(timeWindowRight, (ti me, entryIndex) => time - entryStartTimes[entryIndex]) - 1;
950 var lastDrawOffset = Infinity;
951 for (var entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
952 var entryIndex = levelIndexes[entryIndexOnLevel];
953 var entryStartTime = entryStartTimes[entryIndex];
954 var entryOffsetRight = entryStartTime + (entryTotalTimes[entryIn dex] || 0);
955 if (entryOffsetRight <= timeWindowLeft)
956 break;
957
958 var barX = this._timeToPositionClipped(entryStartTime);
959 // Check if the entry entirely fits into an already drawn pixel, we can just skip drawing it.
960 if (barX >= lastDrawOffset)
961 continue;
962 lastDrawOffset = barX;
963
964 var color = this._dataProvider.entryColor(entryIndex);
965 var bucket = colorBuckets.get(color);
966 if (!bucket) {
967 bucket = [];
968 colorBuckets.set(color, bucket);
969 }
970 bucket.push(entryIndex);
971 }
972 }
973
974 var colors = colorBuckets.keysArray();
975 // We don't use for-of here because it's slow.
976 for (var c = 0; c < colors.length; ++c) {
977 var color = colors[c];
978 var indexes = colorBuckets.get(color);
979 context.beginPath();
980 context.fillStyle = color;
981 for (var i = 0; i < indexes.length; ++i) {
982 var entryIndex = indexes[i];
983 var entryStartTime = entryStartTimes[entryIndex];
984 var barX = this._timeToPositionClipped(entryStartTime);
985 var duration = entryTotalTimes[entryIndex];
986 var barLevel = entryLevels[entryIndex];
987 var barY = this._levelToHeight(barLevel);
988 if (isNaN(duration)) {
989 context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
990 context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math.PI * 2);
991 markerIndices.push(entryIndex);
992 continue;
993 }
994 var barRight = this._timeToPositionClipped(entryStartTime + dura tion);
995 var barWidth = Math.max(barRight - barX, 1);
996 context.rect(barX, barY, barWidth - 0.4, barHeight - 1);
997 if (barWidth > minTextWidth || this._dataProvider.forceDecoratio n(entryIndex))
998 titleIndices.push(entryIndex);
999 }
1000 context.fill();
1001 }
1002
1003 context.strokeStyle = "rgba(0, 0, 0, 0.2)";
1004 context.beginPath();
1005 for (var m = 0; m < markerIndices.length; ++m) {
1006 var entryIndex = markerIndices[m];
1007 var entryStartTime = entryStartTimes[entryIndex];
1008 var barX = this._timeToPositionClipped(entryStartTime);
1009 var barLevel = entryLevels[entryIndex];
1010 var barY = this._levelToHeight(barLevel);
1011 context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
1012 context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math. PI * 2);
1013 }
1014 context.stroke();
1015
1016 context.textBaseline = "alphabetic";
1017 var textBaseHeight = this._barHeight - this._dataProvider.textBaseline() ;
1018
1019 for (var i = 0; i < titleIndices.length; ++i) {
1020 var entryIndex = titleIndices[i];
1021 var entryStartTime = entryStartTimes[entryIndex];
1022 var barX = this._timeToPositionClipped(entryStartTime);
1023 var barRight = Math.min(this._timeToPositionClipped(entryStartTime + entryTotalTimes[entryIndex]), width) + 1;
1024 var barWidth = barRight - barX;
1025 var barLevel = entryLevels[entryIndex];
1026 var barY = this._levelToHeight(barLevel);
1027 var text = this._dataProvider.entryTitle(entryIndex);
1028 if (text && text.length) {
1029 context.font = this._dataProvider.entryFont(entryIndex) || defau ltFont;
1030 text = this._prepareText(context, text, barWidth - 2 * textPaddi ng);
1031 }
1032 var unclippedBarX = this._timeToPosition(entryStartTime);
1033 if (this._dataProvider.decorateEntry(entryIndex, context, text, barX , barY, barWidth, barHeight, unclippedBarX, this._timeToPixel))
1034 continue;
1035 if (!text || !text.length)
1036 continue;
1037 context.fillStyle = this._dataProvider.textColor(entryIndex);
1038 context.fillText(text, barX + textPadding, barY + textBaseHeight);
1039 }
1040
1041 this._drawFlowEvents(context, width, height);
1042
1043 context.restore();
1044
1045 WebInspector.TimelineGrid.drawCanvasGrid(context, this._calculator, 3);
1046 this._drawMarkers();
1047 this._drawGroupHeaders(width, height);
1048
1049 this._updateElementPosition(this._highlightElement, this._highlightedEnt ryIndex);
1050 this._updateElementPosition(this._selectedElement, this._selectedEntryIn dex);
1051 this._updateMarkerHighlight();
1052 },
1053
1054 /**
1055 * @param {number} width
1056 * @param {number} height
1057 */
1058 _drawGroupHeaders: function(width, height)
1059 {
1060 var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.get Context("2d"));
1061 var top = this.scrollOffset();
1062 var ratio = window.devicePixelRatio;
1063 var barHeight = this._barHeight;
1064 var textBaseHeight = barHeight - this._dataProvider.textBaseline();
1065 var groups = this._rawTimelineData.groups || [];
1066 if (!groups.length)
1067 return;
1068
1069 var groupOffsets = this._groupOffsets;
1070 var lastGroupOffset = Array.prototype.peekLast.call(groupOffsets);
1071 var colorUsage = WebInspector.ThemeSupport.ColorUsage;
1072
1073 context.save();
1074 context.scale(ratio, ratio);
1075 context.translate(0, -top);
1076
1077 context.fillStyle = WebInspector.themeSupport.patchColor("#eee", colorUs age.Background);
1078 forEachGroup.call(this, (offset, index, group) => {
1079 var paddingHeight = group.style.padding;
1080 if (paddingHeight < 5)
1081 return;
1082 context.fillRect(0, offset - paddingHeight + 2, width, paddingHeight - 4);
1083 });
1084 if (groups.length && lastGroupOffset < top + height)
1085 context.fillRect(0, lastGroupOffset + 2, width, top + height - lastG roupOffset);
1086
1087 context.strokeStyle = WebInspector.themeSupport.patchColor("#bbb", color Usage.Background);
1088 context.beginPath();
1089 forEachGroup.call(this, (offset, index, group, isFirst) => {
1090 if (isFirst || group.style.padding < 4)
1091 return;
1092 hLine(offset - 2.5);
1093 });
1094 hLine(lastGroupOffset + 0.5);
1095 context.stroke();
1096
1097 forEachGroup.call(this, (offset, index, group) => {
1098 if (group.style.useFirstLineForOverview)
1099 return;
1100 if (!this._isGroupCollapsible(index) || group.expanded) {
1101 if (!group.style.shareHeaderLine) {
1102 context.fillStyle = group.style.backgroundColor;
1103 context.fillRect(0, offset, width, group.style.height);
1104 }
1105 return;
1106 }
1107 var nextGroup = index + 1;
1108 while (nextGroup < groups.length && groups[nextGroup].style.nestingL evel > group.style.nestingLevel)
1109 nextGroup++;
1110 var endLevel = nextGroup < groups.length ? groups[nextGroup].startLe vel : this._dataProvider.maxStackDepth();
1111 this._drawCollapsedOverviewForGroup(offset + 1, group.startLevel, en dLevel);
1112 });
1113
1114 context.save();
1115 forEachGroup.call(this, (offset, index, group) => {
1116 context.font = group.style.font;
1117 if (this._isGroupCollapsible(index) && !group.expanded || group.styl e.shareHeaderLine) {
1118 var width = this._labelWidthForGroup(context, group);
1119 context.fillStyle = WebInspector.Color.parse(group.style.backgro undColor).setAlpha(0.7).asString(null);
1120 context.fillRect(this._headerLeftPadding - this._headerLabelXPad ding, offset + this._headerLabelYPadding, width, barHeight - 2 * this._headerLab elYPadding);
1121 }
1122 context.fillStyle = group.style.color;
1123 context.fillText(group.name, Math.floor(this._expansionArrowIndent * (group.style.nestingLevel + 1) + this._arrowSide), offset + textBaseHeight);
1124 });
1125 context.restore();
1126
1127 context.fillStyle = WebInspector.themeSupport.patchColor("#6e6e6e", colo rUsage.Foreground);
1128 context.beginPath();
1129 forEachGroup.call(this, (offset, index, group) => {
1130 if (this._isGroupCollapsible(index))
1131 drawExpansionArrow.call(this, this._expansionArrowIndent * (grou p.style.nestingLevel + 1), offset + textBaseHeight - this._arrowSide / 2, !!grou p.expanded);
1132 });
1133 context.fill();
1134
1135 context.strokeStyle = WebInspector.themeSupport.patchColor("#ddd", color Usage.Background);
1136 context.beginPath();
1137 context.stroke();
1138
1139 context.restore();
1140
1141 /**
1142 * @param {number} y
1143 */
1144 function hLine(y)
1145 {
1146 context.moveTo(0, y);
1147 context.lineTo(width, y);
1148 }
1149
1150 /**
1151 * @param {number} x
1152 * @param {number} y
1153 * @param {boolean} expanded
1154 * @this {WebInspector.FlameChart}
1155 */
1156 function drawExpansionArrow(x, y, expanded)
1157 {
1158 var arrowHeight = this._arrowSide * Math.sqrt(3) / 2;
1159 var arrowCenterOffset = Math.round(arrowHeight / 2);
1160 context.save();
1161 context.translate(x, y);
1162 context.rotate(expanded ? Math.PI / 2 : 0);
1163 context.moveTo(-arrowCenterOffset, -this._arrowSide / 2);
1164 context.lineTo(-arrowCenterOffset, this._arrowSide / 2);
1165 context.lineTo(arrowHeight - arrowCenterOffset, 0);
1166 context.restore();
1167 }
1168
1169 /**
1170 * @param {function(number, number, !WebInspector.FlameChart.Group, bool ean)} callback
1171 * @this {WebInspector.FlameChart}
1172 */
1173 function forEachGroup(callback)
1174 {
1175 /** @type !Array<{nestingLevel: number, visible: boolean}> */
1176 var groupStack = [{nestingLevel: -1, visible: true}];
1177 for (var i = 0; i < groups.length; ++i) {
1178 var groupTop = groupOffsets[i];
1179 var group = groups[i];
1180 if (groupTop - group.style.padding > top + height)
1181 break;
1182 var firstGroup = true;
1183 while (groupStack.peekLast().nestingLevel >= group.style.nesting Level) {
1184 groupStack.pop();
1185 firstGroup = false;
1186 }
1187 var parentGroupVisible = groupStack.peekLast().visible;
1188 var thisGroupVisible = parentGroupVisible && (!this._isGroupColl apsible(i) || group.expanded);
1189 groupStack.push({nestingLevel: group.style.nestingLevel, visible : thisGroupVisible});
1190 if (!parentGroupVisible || groupTop + group.style.height < top)
1191 continue;
1192 callback(groupTop, i, group, firstGroup);
1193 }
1194 }
1195 },
1196
1197 /**
1198 * @param {!CanvasRenderingContext2D} context
1199 * @param {!WebInspector.FlameChart.Group} group
1200 * @return {number}
1201 */
1202 _labelWidthForGroup: function(context, group)
1203 {
1204 return this._measureWidth(context, group.name) + this._expansionArrowInd ent * (group.style.nestingLevel + 1) + 2 * this._headerLabelXPadding;
1205 },
1206
1207 /**
1208 * @param {number} y
1209 * @param {number} startLevel
1210 * @param {number} endLevel
1211 */
1212 _drawCollapsedOverviewForGroup: function(y, startLevel, endLevel)
1213 {
1214 var range = new WebInspector.SegmentedRange(mergeCallback);
1215 var timeWindowRight = this._timeWindowRight;
1216 var timeWindowLeft = this._timeWindowLeft - this._paddingLeft / this._ti meToPixel;
1217 var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.get Context("2d"));
1218 var barHeight = this._barHeight - 2;
1219 var entryStartTimes = this._rawTimelineData.entryStartTimes;
1220 var entryTotalTimes = this._rawTimelineData.entryTotalTimes;
1221
1222 for (var level = startLevel; level < endLevel; ++level) {
1223 var levelIndexes = this._timelineLevels[level];
1224 var rightIndexOnLevel = levelIndexes.lowerBound(timeWindowRight, (ti me, entryIndex) => time - entryStartTimes[entryIndex]) - 1;
1225 var lastDrawOffset = Infinity;
1226
1227 for (var entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
1228 var entryIndex = levelIndexes[entryIndexOnLevel];
1229 var entryStartTime = entryStartTimes[entryIndex];
1230 var startPosition = this._timeToPositionClipped(entryStartTime);
1231 var entryEndTime = entryStartTime + entryTotalTimes[entryIndex];
1232 if (isNaN(entryEndTime) || startPosition >= lastDrawOffset)
1233 continue;
1234 if (entryEndTime <= timeWindowLeft)
1235 break;
1236 lastDrawOffset = startPosition;
1237 var color = this._dataProvider.entryColor(entryIndex);
1238 range.append(new WebInspector.Segment(startPosition, this._timeT oPositionClipped(entryEndTime), color));
1239 }
1240 }
1241
1242 var segments = range.segments().slice().sort((a, b) => a.data.localeComp are(b.data));
1243 var lastColor;
1244 context.beginPath();
1245 for (var i = 0; i < segments.length; ++i) {
1246 var segment = segments[i];
1247 if (lastColor !== segments[i].data) {
1248 context.fill();
1249 context.beginPath();
1250 lastColor = segments[i].data;
1251 context.fillStyle = lastColor;
1252 }
1253 context.rect(segment.begin, y, segment.end - segment.begin, barHeigh t);
1254 }
1255 context.fill();
1256
1257 /**
1258 * @param {!WebInspector.Segment} a
1259 * @param {!WebInspector.Segment} b
1260 * @return {?WebInspector.Segment}
1261 */
1262 function mergeCallback(a, b)
1263 {
1264 return a.data === b.data && a.end + 0.4 > b.end ? a : null;
1265 }
1266 },
1267
1268 /**
1269 * @param {!CanvasRenderingContext2D} context
1270 * @param {number} height
1271 * @param {number} width
1272 */
1273 _drawFlowEvents: function(context, width, height)
1274 {
1275 var timelineData = this._timelineData();
1276 var timeWindowRight = this._timeWindowRight;
1277 var timeWindowLeft = this._timeWindowLeft;
1278 var flowStartTimes = timelineData.flowStartTimes;
1279 var flowEndTimes = timelineData.flowEndTimes;
1280 var flowStartLevels = timelineData.flowStartLevels;
1281 var flowEndLevels = timelineData.flowEndLevels;
1282 var flowCount = flowStartTimes.length;
1283 var endIndex = flowStartTimes.lowerBound(timeWindowRight);
1284
1285 var color = [];
1286 var fadeColorsCount = 8;
1287 for (var i = 0; i <= fadeColorsCount; ++i)
1288 color[i] = "rgba(128, 0, 0, " + i / fadeColorsCount + ")";
1289 var fadeColorsRange = color.length;
1290 var minimumFlowDistancePx = 15;
1291 var flowArcHeight = 4 * this._barHeight;
1292 var colorIndex = 0;
1293 context.lineWidth = 0.5;
1294 for (var i = 0; i < endIndex; ++i) {
1295 if (flowEndTimes[i] < timeWindowLeft)
1296 continue;
1297 var startX = this._timeToPosition(flowStartTimes[i]);
1298 var endX = this._timeToPosition(flowEndTimes[i]);
1299 if (endX - startX < minimumFlowDistancePx)
1300 continue;
1301 if (startX < -minimumFlowDistancePx && endX > width + minimumFlowDis tancePx)
1302 continue;
1303 // Assign a trasparent color if the flow is small enough or if the p revious color was a transparent color.
1304 if (endX - startX < minimumFlowDistancePx + fadeColorsRange || color Index !== color.length - 1) {
1305 colorIndex = Math.min(fadeColorsRange - 1, Math.floor(endX - sta rtX - minimumFlowDistancePx));
1306 context.strokeStyle = color[colorIndex];
1307 }
1308 var startY = this._levelToHeight(flowStartLevels[i]) + this._barHeig ht;
1309 var endY = this._levelToHeight(flowEndLevels[i]);
1310 context.beginPath();
1311 context.moveTo(startX, startY);
1312 var arcHeight = Math.max(Math.sqrt(Math.abs(startY - endY)), flowArc Height) + 5;
1313 context.bezierCurveTo(startX, startY + arcHeight,
1314 endX, endY + arcHeight,
1315 endX, endY + this._barHeight);
1316 context.stroke();
1317 }
1318 },
1319
1320 _drawMarkers: function()
1321 {
1322 var markers = this._timelineData().markers;
1323 var left = this._markerIndexBeforeTime(this._calculator.minimumBoundary( ));
1324 var rightBoundary = this._calculator.maximumBoundary();
1325
1326 var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.get Context("2d"));
1327 context.save();
1328 var ratio = window.devicePixelRatio;
1329 context.scale(ratio, ratio);
1330 var height = WebInspector.FlameChart.DividersBarHeight - 1;
1331 for (var i = left; i < markers.length; i++) {
1332 var timestamp = markers[i].startTime();
1333 if (timestamp > rightBoundary)
1334 break;
1335 markers[i].draw(context, this._calculator.computePosition(timestamp) , height, this._timeToPixel);
1336 }
1337 context.restore();
1338 },
1339
1340 _updateMarkerHighlight: function()
1341 {
1342 var element = this._markerHighlighElement;
1343 if (element.parentElement)
1344 element.remove();
1345 var markerIndex = this._highlightedMarkerIndex;
1346 if (markerIndex === -1)
1347 return;
1348 var marker = this._timelineData().markers[markerIndex];
1349 var barX = this._timeToPositionClipped(marker.startTime());
1350 element.title = marker.title();
1351 var style = element.style;
1352 style.left = barX + "px";
1353 style.backgroundColor = marker.color();
1354 this.viewportElement.appendChild(element);
1355 },
1356
1357 /**
1358 * @param {?WebInspector.FlameChart.TimelineData} timelineData
1359 */
1360 _processTimelineData: function(timelineData)
1361 {
1362 if (!timelineData) {
1363 this._timelineLevels = null;
1364 this._visibleLevelOffsets = null;
1365 this._visibleLevels = null;
1366 this._groupOffsets = null;
1367 this._rawTimelineData = null;
1368 this._rawTimelineDataLength = 0;
1369 return;
1370 }
1371
1372 this._rawTimelineData = timelineData;
1373 this._rawTimelineDataLength = timelineData.entryStartTimes.length;
1374
1375 var entryCounters = new Uint32Array(this._dataProvider.maxStackDepth() + 1);
1376 for (var i = 0; i < timelineData.entryLevels.length; ++i)
1377 ++entryCounters[timelineData.entryLevels[i]];
1378 var levelIndexes = new Array(entryCounters.length);
1379 for (var i = 0; i < levelIndexes.length; ++i) {
1380 levelIndexes[i] = new Uint32Array(entryCounters[i]);
1381 entryCounters[i] = 0;
1382 }
1383 for (var i = 0; i < timelineData.entryLevels.length; ++i) {
1384 var level = timelineData.entryLevels[i];
1385 levelIndexes[level][entryCounters[level]++] = i;
1386 }
1387 this._timelineLevels = levelIndexes;
1388 var groups = this._rawTimelineData.groups || [];
1389 for (var i = 0; i < groups.length; ++i) {
1390 var expanded = this._groupExpansionState[groups[i].name];
1391 if (expanded !== undefined)
1392 groups[i].expanded = expanded;
1393 }
1394 this._updateLevelPositions();
1395 this._updateHeight();
1396 },
1397
1398 _updateLevelPositions: function()
1399 {
1400 var levelCount = this._dataProvider.maxStackDepth();
1401 var groups = this._rawTimelineData.groups || [];
1402 this._visibleLevelOffsets = new Uint32Array(levelCount + 1);
1403 this._visibleLevels = new Uint16Array(levelCount);
1404 this._groupOffsets = new Uint32Array(groups.length + 1);
1405
1406 var groupIndex = -1;
1407 var currentOffset = WebInspector.FlameChart.DividersBarHeight;
1408 var visible = true;
1409 /** @type !Array<{nestingLevel: number, visible: boolean}> */
1410 var groupStack = [{nestingLevel: -1, visible: true}];
1411 for (var level = 0; level < levelCount; ++level) {
1412 while (groupIndex < groups.length - 1 && level === groups[groupIndex + 1].startLevel) {
1413 ++groupIndex;
1414 var style = groups[groupIndex].style;
1415 var nextLevel = true;
1416 while (groupStack.peekLast().nestingLevel >= style.nestingLevel) {
1417 groupStack.pop();
1418 nextLevel = false;
1419 }
1420 var thisGroupIsVisible = groupIndex >= 0 && this._isGroupCollaps ible(groupIndex) ? groups[groupIndex].expanded : true;
1421 var parentGroupIsVisible = groupStack.peekLast().visible;
1422 visible = thisGroupIsVisible && parentGroupIsVisible;
1423 groupStack.push({nestingLevel: style.nestingLevel, visible: visi ble});
1424 if (parentGroupIsVisible)
1425 currentOffset += nextLevel ? 0 : style.padding;
1426 this._groupOffsets[groupIndex] = currentOffset;
1427 if (parentGroupIsVisible && !style.shareHeaderLine)
1428 currentOffset += style.height;
1429 }
1430 var isFirstOnLevel = groupIndex >= 0 && level === groups[groupIndex] .startLevel;
1431 var thisLevelIsVisible = visible || isFirstOnLevel && groups[groupIn dex].style.useFirstLineForOverview;
1432 this._visibleLevels[level] = thisLevelIsVisible;
1433 this._visibleLevelOffsets[level] = currentOffset;
1434 if (thisLevelIsVisible || (parentGroupIsVisible && style.shareHeader Line && isFirstOnLevel))
1435 currentOffset += this._barHeight;
1436 }
1437 if (groupIndex >= 0)
1438 this._groupOffsets[groupIndex + 1] = currentOffset;
1439 this._visibleLevelOffsets[level] = currentOffset;
1440 },
1441
1442 /**
1443 * @param {number} index
1444 */
1445 _isGroupCollapsible: function(index)
1446 {
1447 var groups = this._rawTimelineData.groups || [];
1448 var style = groups[index].style;
1449 if (!style.shareHeaderLine || !style.collapsible)
1450 return !!style.collapsible;
1451 var isLastGroup = index + 1 >= groups.length;
1452 if (!isLastGroup && groups[index + 1].style.nestingLevel > style.nesting Level)
1453 return true;
1454 var nextGroupLevel = isLastGroup ? this._dataProvider.maxStackDepth() : groups[index + 1].startLevel;
1455 // For groups that only have one line and share header line, pretend the se are not collapsible.
1456 return nextGroupLevel !== groups[index].startLevel + 1;
1457 },
1458
1459 /**
1460 * @param {number} entryIndex
1461 */
1462 setSelectedEntry: function(entryIndex)
1463 {
1464 if (entryIndex === -1 && !this.isDragging())
1465 this.hideRangeSelection();
1466 if (this._selectedEntryIndex === entryIndex)
1467 return;
1468 this._selectedEntryIndex = entryIndex;
1469 this._revealEntry(entryIndex);
1470 this._updateElementPosition(this._selectedElement, this._selectedEntryIn dex);
1471 },
1472
1473 /**
1474 * @param {!Element} element
1475 * @param {number} entryIndex
1476 */
1477 _updateElementPosition: function(element, entryIndex)
1478 {
1479 const elementMinWidthPx = 2;
1480 if (element.parentElement)
1481 element.remove();
1482 if (entryIndex === -1)
1483 return;
1484 var timelineData = this._timelineData();
1485 var startTime = timelineData.entryStartTimes[entryIndex];
1486 var endTime = startTime + (timelineData.entryTotalTimes[entryIndex] || 0 );
1487 var barX = this._timeToPositionClipped(startTime);
1488 var barRight = this._timeToPositionClipped(endTime);
1489 if (barRight === 0 || barX === this._offsetWidth)
1490 return;
1491 var barWidth = barRight - barX;
1492 var barCenter = barX + barWidth / 2;
1493 barWidth = Math.max(barWidth, elementMinWidthPx);
1494 barX = barCenter - barWidth / 2;
1495 var barY = this._levelToHeight(timelineData.entryLevels[entryIndex]) - t his.scrollOffset();
1496 var style = element.style;
1497 style.left = barX + "px";
1498 style.top = barY + "px";
1499 style.width = barWidth + "px";
1500 style.height = this._barHeight - 1 + "px";
1501 this.viewportElement.appendChild(element);
1502 },
1503
1504 /**
1505 * @param {number} time
1506 * @return {number}
1507 */
1508 _timeToPositionClipped: function(time)
1509 {
1510 return Number.constrain(this._timeToPosition(time), 0, this._offsetWidth );
1511 },
1512
1513 /**
1514 * @param {number} time
1515 * @return {number}
1516 */
1517 _timeToPosition: function(time)
1518 {
1519 return Math.floor((time - this._minimumBoundary) * this._timeToPixel) - this._pixelWindowLeft + this._paddingLeft;
1520 },
1521
1522 /**
1523 * @param {number} level
1524 * @return {number}
1525 */
1526 _levelToHeight: function(level)
1527 {
1528 return this._visibleLevelOffsets[level];
1529 },
1530
1531 /**
1532 * @param {!CanvasRenderingContext2D} context
1533 * @param {string} text
1534 * @param {number} maxWidth
1535 * @return {string}
1536 */
1537 _prepareText: function(context, text, maxWidth)
1538 {
1539 var /** @const */ maxLength = 200;
1540 if (maxWidth <= 10)
1541 return "";
1542 if (text.length > maxLength)
1543 text = text.trimMiddle(maxLength);
1544 var textWidth = this._measureWidth(context, text);
1545 if (textWidth <= maxWidth)
1546 return text;
1547
1548 var l = 0;
1549 var r = text.length;
1550 var lv = 0;
1551 var rv = textWidth;
1552 while (l < r && lv !== rv && lv !== maxWidth) {
1553 var m = Math.ceil(l + (r - l) * (maxWidth - lv) / (rv - lv));
1554 var mv = this._measureWidth(context, text.trimMiddle(m));
1555 if (mv <= maxWidth) {
1556 l = m;
1557 lv = mv;
1558 } else {
1559 r = m - 1;
1560 rv = mv;
1561 }
1562 }
1563 text = text.trimMiddle(l);
1564 return text !== "\u2026" ? text : "";
1565 },
1566
1567 /**
1568 * @param {!CanvasRenderingContext2D} context
1569 * @param {string} text
1570 * @return {number}
1571 */
1572 _measureWidth: function(context, text)
1573 {
1574 var /** @const */ maxCacheableLength = 200;
1575 if (text.length > maxCacheableLength)
1576 return context.measureText(text).width;
1577
1578 var font = context.font;
1579 var textWidths = this._textWidth.get(font);
1580 if (!textWidths) {
1581 textWidths = new Map();
1582 this._textWidth.set(font, textWidths);
1583 }
1584 var width = textWidths.get(text);
1585 if (!width) {
1586 width = context.measureText(text).width;
1587 textWidths.set(text, width);
1588 }
1589 return width;
1590 },
1591
1592 _updateBoundaries: function()
1593 {
1594 this._totalTime = this._dataProvider.totalTime();
1595 this._minimumBoundary = this._dataProvider.minimumBoundary();
1596
1597 var windowWidth = 1;
1598 if (this._timeWindowRight !== Infinity) {
1599 this._windowLeft = (this._timeWindowLeft - this._minimumBoundary) / this._totalTime;
1600 this._windowRight = (this._timeWindowRight - this._minimumBoundary) / this._totalTime;
1601 windowWidth = this._windowRight - this._windowLeft;
1602 } else if (this._timeWindowLeft === Infinity) {
1603 this._windowLeft = Infinity;
1604 this._windowRight = Infinity;
1605 } else {
1606 this._windowLeft = 0;
1607 this._windowRight = 1;
1608 }
1609
1610 var totalPixels = Math.floor((this._offsetWidth - this._paddingLeft) / w indowWidth);
1611 this._pixelWindowLeft = Math.floor(totalPixels * this._windowLeft);
1612
1613 this._timeToPixel = totalPixels / this._totalTime;
1614 this._pixelToTime = this._totalTime / totalPixels;
1615 },
1616
1617 _updateHeight: function()
1618 {
1619 var height = this._levelToHeight(this._dataProvider.maxStackDepth());
1620 this.setContentHeight(height);
1621 },
1622
1623 /**
1624 * @override
1625 */
1626 onResize: function()
1627 {
1628 WebInspector.FlameChart.prototype.__proto__.onResize.call(this);
1629 this.scheduleUpdate();
1630 },
1631
1632 /**
1633 * @override
1634 */
1635 update: function()
1636 {
1637 if (!this._timelineData())
1638 return;
1639 this._resetCanvas();
1640 this._updateHeight();
1641 this._updateBoundaries();
1642 this._calculator._updateBoundaries(this);
1643 this._draw(this._offsetWidth, this._offsetHeight);
1644 if (!this.isDragging())
1645 this._updateHighlight();
1646 },
1647
1648 reset: function()
1649 {
1650 WebInspector.FlameChart.prototype.__proto__.reset.call(this);
1651 this._highlightedMarkerIndex = -1;
1652 this._highlightedEntryIndex = -1;
1653 this._selectedEntryIndex = -1;
1654 /** @type {!Map<string,!Map<string,number>>} */
1655 this._textWidth = new Map();
1656 this.update();
1657 },
1658
1659 _enabled: function()
1660 {
1661 return this._rawTimelineDataLength !== 0;
1662 },
1663
1664 __proto__: WebInspector.ChartViewport.prototype
1665 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698