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

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

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

Powered by Google App Engine
This is Rietveld 408576698