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

Side by Side Diff: Source/devtools/front_end/components/FlameChart.js

Issue 714423005: DevTools: move front-end files from components to ui. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: review comment addressed Created 6 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 | Annotate | Revision Log
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 WebInspector.FlameChartDelegate = function() { }
35
36 WebInspector.FlameChartDelegate.prototype = {
37 /**
38 * @param {number} startTime
39 * @param {number} endTime
40 */
41 requestWindowTimes: function(startTime, endTime) { },
42
43 /**
44 * @param {number} startTime
45 * @param {number} endTime
46 */
47 updateBoxSelection: function(startTime, endTime) { }
48 }
49
50 /**
51 * @constructor
52 * @extends {WebInspector.HBox}
53 * @param {!WebInspector.FlameChartDataProvider} dataProvider
54 * @param {!WebInspector.FlameChartDelegate} flameChartDelegate
55 * @param {boolean} isTopDown
56 */
57 WebInspector.FlameChart = function(dataProvider, flameChartDelegate, isTopDown)
58 {
59 WebInspector.HBox.call(this, true);
60 this.contentElement.appendChild(WebInspector.View.createStyleElement("compon ents/flameChart.css"));
61 this.contentElement.classList.add("flame-chart-main-pane");
62 this._flameChartDelegate = flameChartDelegate;
63 this._isTopDown = isTopDown;
64
65 this._calculator = new WebInspector.FlameChart.Calculator();
66
67 this._canvas = this.contentElement.createChild("canvas");
68 this._canvas.tabIndex = 1;
69 this.setDefaultFocusedElement(this._canvas);
70 this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), fal se);
71 this._canvas.addEventListener("mousewheel", this._onMouseWheel.bind(this), f alse);
72 this._canvas.addEventListener("click", this._onClick.bind(this), false);
73 this._canvas.addEventListener("keydown", this._onKeyDown.bind(this), false);
74 WebInspector.installDragHandle(this._canvas, this._startCanvasDragging.bind( this), this._canvasDragging.bind(this), this._endCanvasDragging.bind(this), "mov e", null);
75
76 this._vScrollElement = this.contentElement.createChild("div", "flame-chart-v -scroll");
77 this._vScrollContent = this._vScrollElement.createChild("div");
78 this._vScrollElement.addEventListener("scroll", this.scheduleUpdate.bind(thi s), false);
79
80 this._entryInfo = this.contentElement.createChild("div", "flame-chart-entry- info");
81 this._markerHighlighElement = this.contentElement.createChild("div", "flame- chart-marker-highlight-element");
82 this._highlightElement = this.contentElement.createChild("div", "flame-chart -highlight-element");
83 this._selectedElement = this.contentElement.createChild("div", "flame-chart- selected-element");
84 this._selectionOverlay = this.contentElement.createChild("div", "flame-chart -selection-overlay hidden");
85 this._selectedTimeSpanLabel = this._selectionOverlay.createChild("div", "tim e-span");
86
87 this._dataProvider = dataProvider;
88
89 this._windowLeft = 0.0;
90 this._windowRight = 1.0;
91 this._windowWidth = 1.0;
92 this._timeWindowLeft = 0;
93 this._timeWindowRight = Infinity;
94 this._barHeight = dataProvider.barHeight();
95 this._barHeightDelta = this._isTopDown ? -this._barHeight : this._barHeight;
96 this._minWidth = 1;
97 this._paddingLeft = this._dataProvider.paddingLeft();
98 this._markerPadding = 2;
99 this._markerRadius = this._barHeight / 2 - this._markerPadding;
100 this._highlightedMarkerIndex = -1;
101 this._highlightedEntryIndex = -1;
102 this._selectedEntryIndex = -1;
103 this._rawTimelineDataLength = 0;
104 this._textWidth = {};
105 }
106
107 WebInspector.FlameChart.DividersBarHeight = 20;
108
109 WebInspector.FlameChart.MinimalTimeWindowMs = 0.01;
110
111 /**
112 * @interface
113 */
114 WebInspector.FlameChartDataProvider = function()
115 {
116 }
117
118 /**
119 * @constructor
120 * @param {!Array.<number>|!Uint8Array} entryLevels
121 * @param {!Array.<number>|!Float32Array} entryTotalTimes
122 * @param {!Array.<number>|!Float64Array} entryStartTimes
123 */
124 WebInspector.FlameChart.TimelineData = function(entryLevels, entryTotalTimes, en tryStartTimes)
125 {
126 this.entryLevels = entryLevels;
127 this.entryTotalTimes = entryTotalTimes;
128 this.entryStartTimes = entryStartTimes;
129 /** @type {!Array.<number>} */
130 this.markerTimestamps = [];
131 }
132
133 WebInspector.FlameChartDataProvider.prototype = {
134 /**
135 * @return {number}
136 */
137 barHeight: function() { },
138
139 /**
140 * @param {number} startTime
141 * @param {number} endTime
142 * @return {?Array.<number>}
143 */
144 dividerOffsets: function(startTime, endTime) { },
145
146 /**
147 * @param {number} index
148 * @return {string}
149 */
150 markerColor: function(index) { },
151
152 /**
153 * @param {number} index
154 * @return {string}
155 */
156 markerTitle: function(index) { },
157
158 /**
159 * @param {number} index
160 * @return {boolean}
161 */
162 isTallMarker: function(index) { },
163
164 /**
165 * @return {number}
166 */
167 minimumBoundary: function() { },
168
169 /**
170 * @return {number}
171 */
172 totalTime: function() { },
173
174 /**
175 * @return {number}
176 */
177 maxStackDepth: function() { },
178
179 /**
180 * @return {?WebInspector.FlameChart.TimelineData}
181 */
182 timelineData: function() { },
183
184 /**
185 * @param {number} entryIndex
186 * @return {?Array.<!{title: string, text: string}>}
187 */
188 prepareHighlightedEntryInfo: function(entryIndex) { },
189
190 /**
191 * @param {number} entryIndex
192 * @return {boolean}
193 */
194 canJumpToEntry: function(entryIndex) { },
195
196 /**
197 * @param {number} entryIndex
198 * @return {?string}
199 */
200 entryTitle: function(entryIndex) { },
201
202 /**
203 * @param {number} entryIndex
204 * @return {?string}
205 */
206 entryFont: function(entryIndex) { },
207
208 /**
209 * @param {number} entryIndex
210 * @return {string}
211 */
212 entryColor: function(entryIndex) { },
213
214 /**
215 * @param {number} entryIndex
216 * @param {!CanvasRenderingContext2D} context
217 * @param {?string} text
218 * @param {number} barX
219 * @param {number} barY
220 * @param {number} barWidth
221 * @param {number} barHeight
222 * @param {function(number):number} timeToPosition
223 * @return {boolean}
224 */
225 decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, bar Height, timeToPosition) { },
226
227 /**
228 * @param {number} entryIndex
229 * @return {boolean}
230 */
231 forceDecoration: function(entryIndex) { },
232
233 /**
234 * @param {number} entryIndex
235 * @return {string}
236 */
237 textColor: function(entryIndex) { },
238
239 /**
240 * @return {number}
241 */
242 textBaseline: function() { },
243
244 /**
245 * @return {number}
246 */
247 textPadding: function() { },
248
249 /**
250 * @return {?{startTime: number, endTime: number}}
251 */
252 highlightTimeRange: function(entryIndex) { },
253
254 /**
255 * @return {number}
256 */
257 paddingLeft: function() { },
258 }
259
260 WebInspector.FlameChart.Events = {
261 EntrySelected: "EntrySelected"
262 }
263
264
265 /**
266 * @constructor
267 * @param {!{min: number, max: number, count: number}|number=} hueSpace
268 * @param {!{min: number, max: number, count: number}|number=} satSpace
269 * @param {!{min: number, max: number, count: number}|number=} lightnessSpace
270 * @param {!{min: number, max: number, count: number}|number=} alphaSpace
271 */
272 WebInspector.FlameChart.ColorGenerator = function(hueSpace, satSpace, lightnessS pace, alphaSpace)
273 {
274 this._hueSpace = hueSpace || { min: 0, max: 360, count: 20 };
275 this._satSpace = satSpace || 67;
276 this._lightnessSpace = lightnessSpace || 80;
277 this._alphaSpace = alphaSpace || 1;
278 this._colors = {};
279 }
280
281 WebInspector.FlameChart.ColorGenerator.prototype = {
282 /**
283 * @param {string} id
284 * @param {string|!CanvasGradient} color
285 */
286 setColorForID: function(id, color)
287 {
288 this._colors[id] = color;
289 },
290
291 /**
292 * @param {string} id
293 * @return {string}
294 */
295 colorForID: function(id)
296 {
297 var color = this._colors[id];
298 if (!color) {
299 color = this._generateColorForID(id);
300 this._colors[id] = color;
301 }
302 return color;
303 },
304
305 /**
306 * @param {string} id
307 * @return {string}
308 */
309 _generateColorForID: function(id)
310 {
311 var hash = id.hashCode();
312 var h = this._indexToValueInSpace(hash, this._hueSpace);
313 var s = this._indexToValueInSpace(hash, this._satSpace);
314 var l = this._indexToValueInSpace(hash, this._lightnessSpace);
315 var a = this._indexToValueInSpace(hash, this._alphaSpace);
316 return "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")";
317 },
318
319 /**
320 * @param {number} index
321 * @param {!{min: number, max: number, count: number}|number} space
322 * @return {number}
323 */
324 _indexToValueInSpace: function(index, space)
325 {
326 if (typeof space === "number")
327 return space;
328 index %= space.count;
329 return space.min + Math.floor(index / space.count * (space.max - space.m in));
330 }
331 }
332
333
334 /**
335 * @constructor
336 * @implements {WebInspector.TimelineGrid.Calculator}
337 */
338 WebInspector.FlameChart.Calculator = function()
339 {
340 this._paddingLeft = 0;
341 }
342
343 WebInspector.FlameChart.Calculator.prototype = {
344 /**
345 * @return {number}
346 */
347 paddingLeft: function()
348 {
349 return this._paddingLeft;
350 },
351
352 /**
353 * @param {!WebInspector.FlameChart} mainPane
354 */
355 _updateBoundaries: function(mainPane)
356 {
357 this._totalTime = mainPane._dataProvider.totalTime();
358 this._zeroTime = mainPane._dataProvider.minimumBoundary();
359 this._minimumBoundaries = this._zeroTime + mainPane._windowLeft * this._ totalTime;
360 this._maximumBoundaries = this._zeroTime + mainPane._windowRight * this. _totalTime;
361 this._paddingLeft = mainPane._paddingLeft;
362 this._width = mainPane._canvas.width / window.devicePixelRatio - this._p addingLeft;
363 this._timeToPixel = this._width / this.boundarySpan();
364 },
365
366 /**
367 * @param {number} time
368 * @return {number}
369 */
370 computePosition: function(time)
371 {
372 return Math.round((time - this._minimumBoundaries) * this._timeToPixel + this._paddingLeft);
373 },
374
375 /**
376 * @param {number} value
377 * @param {number=} precision
378 * @return {string}
379 */
380 formatTime: function(value, precision)
381 {
382 return Number.preciseMillisToString(value - this._zeroTime, precision);
383 },
384
385 /**
386 * @return {number}
387 */
388 maximumBoundary: function()
389 {
390 return this._maximumBoundaries;
391 },
392
393 /**
394 * @return {number}
395 */
396 minimumBoundary: function()
397 {
398 return this._minimumBoundaries;
399 },
400
401 /**
402 * @return {number}
403 */
404 zeroTime: function()
405 {
406 return this._zeroTime;
407 },
408
409 /**
410 * @return {number}
411 */
412 boundarySpan: function()
413 {
414 return this._maximumBoundaries - this._minimumBoundaries;
415 }
416 }
417
418 WebInspector.FlameChart.prototype = {
419 _resetCanvas: function()
420 {
421 var ratio = window.devicePixelRatio;
422 this._canvas.width = this._offsetWidth * ratio;
423 this._canvas.height = this._offsetHeight * ratio;
424 this._canvas.style.width = this._offsetWidth + "px";
425 this._canvas.style.height = this._offsetHeight + "px";
426 },
427
428 /**
429 * @return {?WebInspector.FlameChart.TimelineData}
430 */
431 _timelineData: function()
432 {
433 var timelineData = this._dataProvider.timelineData();
434 if (timelineData !== this._rawTimelineData || timelineData.entryStartTim es.length !== this._rawTimelineDataLength)
435 this._processTimelineData(timelineData);
436 return this._rawTimelineData;
437 },
438
439 _cancelAnimation: function()
440 {
441 if (this._cancelWindowTimesAnimation) {
442 this._timeWindowLeft = this._pendingAnimationTimeLeft;
443 this._timeWindowRight = this._pendingAnimationTimeRight;
444 this._cancelWindowTimesAnimation();
445 delete this._cancelWindowTimesAnimation;
446 }
447 },
448
449 _revealEntry: function(entryIndex)
450 {
451 var timelineData = this._timelineData();
452 if (!timelineData)
453 return;
454 var entryStartTime = timelineData.entryStartTimes[entryIndex];
455 var entryTotalTime = timelineData.entryTotalTimes[entryIndex];
456 var entryEndTime = entryStartTime + entryTotalTime;
457 var minEntryTimeWindow = Math.min(entryTotalTime, this._timeWindowRight - this._timeWindowLeft);
458
459 var y = this._levelToHeight(timelineData.entryLevels[entryIndex]);
460 if (y < this._vScrollElement.scrollTop)
461 this._vScrollElement.scrollTop = y;
462 else if (y > this._vScrollElement.scrollTop + this._offsetHeight + this. _barHeightDelta)
463 this._vScrollElement.scrollTop = y - this._offsetHeight - this._barH eightDelta;
464
465 if (this._timeWindowLeft > entryEndTime) {
466 var delta = this._timeWindowLeft - entryEndTime + minEntryTimeWindow ;
467 this._flameChartDelegate.requestWindowTimes(this._timeWindowLeft - d elta, this._timeWindowRight - delta);
468 } else if (this._timeWindowRight < entryStartTime) {
469 var delta = entryStartTime - this._timeWindowRight + minEntryTimeWin dow;
470 this._flameChartDelegate.requestWindowTimes(this._timeWindowLeft + d elta, this._timeWindowRight + delta);
471 }
472 },
473
474 /**
475 * @param {number} startTime
476 * @param {number} endTime
477 */
478 setWindowTimes: function(startTime, endTime)
479 {
480 if (this._muteAnimation || this._timeWindowLeft === 0 || this._timeWindo wRight === Infinity || (startTime === 0 && endTime === Infinity)) {
481 // Initial setup.
482 this._timeWindowLeft = startTime;
483 this._timeWindowRight = endTime;
484 this.scheduleUpdate();
485 return;
486 }
487
488 this._cancelAnimation();
489 this._cancelWindowTimesAnimation = WebInspector.animateFunction(this._an imateWindowTimes.bind(this),
490 [{from: this._timeWindowLeft, to: startTime}, {from: this._timeWindo wRight, to: endTime}], 5,
491 this._animationCompleted.bind(this));
492 this._pendingAnimationTimeLeft = startTime;
493 this._pendingAnimationTimeRight = endTime;
494 },
495
496 /**
497 * @param {number} startTime
498 * @param {number} endTime
499 */
500 _animateWindowTimes: function(startTime, endTime)
501 {
502 this._timeWindowLeft = startTime;
503 this._timeWindowRight = endTime;
504 this.update();
505 },
506
507 _animationCompleted: function()
508 {
509 delete this._cancelWindowTimesAnimation;
510 },
511
512 /**
513 * @param {!MouseEvent} event
514 */
515 _startCanvasDragging: function(event)
516 {
517 if (event.shiftKey) {
518 this._startBoxSelection(event);
519 this._isDragging = true;
520 return true;
521 }
522 if (!this._timelineData() || this._timeWindowRight === Infinity)
523 return false;
524 this._isDragging = true;
525 this._maxDragOffset = 0;
526 this._dragStartPointX = event.pageX;
527 this._dragStartPointY = event.pageY;
528 this._dragStartScrollTop = this._vScrollElement.scrollTop;
529 this._dragStartWindowLeft = this._timeWindowLeft;
530 this._dragStartWindowRight = this._timeWindowRight;
531 this._canvas.style.cursor = "";
532
533 return true;
534 },
535
536 /**
537 * @param {!MouseEvent} event
538 */
539 _canvasDragging: function(event)
540 {
541 if (this._isSelecting) {
542 this._updateBoxSelection(event);
543 return;
544 }
545 var pixelShift = this._dragStartPointX - event.pageX;
546 this._dragStartPointX = event.pageX;
547 this._muteAnimation = true;
548 this._handlePanGesture(pixelShift * this._pixelToTime);
549 this._muteAnimation = false;
550
551 var pixelScroll = this._dragStartPointY - event.pageY;
552 this._vScrollElement.scrollTop = this._dragStartScrollTop + pixelScroll;
553 this._maxDragOffset = Math.max(this._maxDragOffset, Math.abs(pixelShift) );
554 },
555
556 _endCanvasDragging: function()
557 {
558 this._hideBoxSelection();
559 this._isDragging = false;
560 },
561
562 /**
563 * @param {!MouseEvent} event
564 */
565 _startBoxSelection: function(event)
566 {
567 this._selectionOffsetShiftX = event.offsetX - event.pageX;
568 this._selectionOffsetShiftY = event.offsetY - event.pageY;
569 this._selectionStartX = event.offsetX;
570 this._selectionStartY = event.offsetY;
571 this._isSelecting = true;
572 var style = this._selectionOverlay.style;
573 style.left = this._selectionStartX + "px";
574 style.top = this._selectionStartY + "px";
575 style.width = "1px";
576 style.height = "1px";
577 this._selectedTimeSpanLabel.textContent = "";
578 this._selectionOverlay.classList.remove("hidden");
579 },
580
581 _hideBoxSelection: function()
582 {
583 this._selectionOffsetShiftX = null;
584 this._selectionOffsetShiftY = null;
585 this._selectionStartX = null;
586 this._selectionStartY = null;
587 this._isSelecting = false;
588 this._selectionOverlay.classList.add("hidden");
589 },
590
591 /**
592 * @param {!MouseEvent} event
593 */
594 _updateBoxSelection: function(event)
595 {
596 var x = event.pageX + this._selectionOffsetShiftX;
597 var y = event.pageY + this._selectionOffsetShiftY;
598 x = Number.constrain(x, 0, this._offsetWidth);
599 y = Number.constrain(y, 0, this._offsetHeight);
600 var style = this._selectionOverlay.style;
601 style.left = Math.min(x, this._selectionStartX) + "px";
602 style.top = Math.min(y, this._selectionStartY) + "px";
603 var selectionWidth = Math.abs(x - this._selectionStartX)
604 style.width = selectionWidth + "px";
605 style.height = Math.abs(y - this._selectionStartY) + "px";
606
607 var timeSpan = selectionWidth * this._pixelToTime;
608 this._selectedTimeSpanLabel.textContent = Number.preciseMillisToString( timeSpan, 2);
609 var start = this._cursorTime(this._selectionStartX);
610 var end = this._cursorTime(x);
611 if (end > start)
612 this._flameChartDelegate.updateBoxSelection(start, end);
613 else
614 this._flameChartDelegate.updateBoxSelection(end, start);
615 },
616
617 /**
618 * @param {!Event} event
619 */
620 _onMouseMove: function(event)
621 {
622 this._lastMouseOffsetX = event.offsetX;
623
624 if (!this._enabled())
625 return;
626
627 if (this._isDragging)
628 return;
629
630 var inDividersBar = event.offsetY < WebInspector.FlameChart.DividersBarH eight;
631 this._highlightedMarkerIndex = inDividersBar ? this._markerIndexAtPositi on(event.offsetX) : -1;
632 this._updateMarkerHighlight();
633 if (inDividersBar)
634 return;
635
636 var entryIndex = this._coordinatesToEntryIndex(event.offsetX, event.offs etY);
637
638 if (this._highlightedEntryIndex === entryIndex)
639 return;
640
641 if (entryIndex === -1 || !this._dataProvider.canJumpToEntry(entryIndex))
642 this._canvas.style.cursor = "default";
643 else
644 this._canvas.style.cursor = "pointer";
645
646 this._highlightedEntryIndex = entryIndex;
647
648 this._updateElementPosition(this._highlightElement, this._highlightedEnt ryIndex);
649 this._entryInfo.removeChildren();
650
651 if (this._highlightedEntryIndex === -1)
652 return;
653
654 if (!this._isDragging) {
655 var entryInfo = this._dataProvider.prepareHighlightedEntryInfo(this. _highlightedEntryIndex);
656 if (entryInfo)
657 this._entryInfo.appendChild(this._buildEntryInfo(entryInfo));
658 }
659 },
660
661 _onClick: function()
662 {
663 this.focus();
664 // onClick comes after dragStart and dragEnd events.
665 // So if there was drag (mouse move) in the middle of that events
666 // we skip the click. Otherwise we jump to the sources.
667 const clickThreshold = 5;
668 if (this._maxDragOffset > clickThreshold)
669 return;
670 if (this._highlightedEntryIndex === -1)
671 return;
672 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelect ed, this._highlightedEntryIndex);
673 },
674
675 /**
676 * @param {!Event} e
677 */
678 _onMouseWheel: function(e)
679 {
680 if (!this._enabled())
681 return;
682 // Pan vertically when shift down only.
683 var panVertically = e.shiftKey && (e.wheelDeltaY || Math.abs(e.wheelDelt aX) === 120);
684 var panHorizontally = Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY) && !e.shiftKey;
685 if (panVertically) {
686 this._vScrollElement.scrollTop -= (e.wheelDeltaY || e.wheelDeltaX) / 120 * this._offsetHeight / 8;
687 } else if (panHorizontally) {
688 var shift = -e.wheelDeltaX * this._pixelToTime;
689 this._muteAnimation = true;
690 this._handlePanGesture(shift);
691 this._muteAnimation = false;
692 } else { // Zoom.
693 const mouseWheelZoomSpeed = 1 / 120;
694 this._handleZoomGesture(Math.pow(1.2, -(e.wheelDeltaY || e.wheelDelt aX) * mouseWheelZoomSpeed) - 1);
695 }
696
697 // Block swipe gesture.
698 e.consume(true);
699 },
700
701 /**
702 * @param {!Event} e
703 */
704 _onKeyDown: function(e)
705 {
706 if (e.altKey || e.ctrlKey || e.metaKey)
707 return;
708 var zoomMultiplier = e.shiftKey ? 0.8 : 0.3;
709 var panMultiplier = e.shiftKey ? 320 : 80;
710 if (e.keyCode === "A".charCodeAt(0)) {
711 this._handlePanGesture(-panMultiplier * this._pixelToTime);
712 e.consume(true);
713 } else if (e.keyCode === "D".charCodeAt(0)) {
714 this._handlePanGesture(panMultiplier * this._pixelToTime);
715 e.consume(true);
716 } else if (e.keyCode === "W".charCodeAt(0)) {
717 this._handleZoomGesture(-zoomMultiplier);
718 e.consume(true);
719 } else if (e.keyCode === "S".charCodeAt(0)) {
720 this._handleZoomGesture(zoomMultiplier);
721 e.consume(true);
722 }
723 },
724
725 /**
726 * @param {number} zoom
727 */
728 _handleZoomGesture: function(zoom)
729 {
730 this._cancelAnimation();
731 var bounds = this._windowForGesture();
732 var cursorTime = this._cursorTime(this._lastMouseOffsetX);
733 bounds.left += (bounds.left - cursorTime) * zoom;
734 bounds.right += (bounds.right - cursorTime) * zoom;
735 this._requestWindowTimes(bounds);
736 },
737
738 /**
739 * @param {number} shift
740 */
741 _handlePanGesture: function(shift)
742 {
743 this._cancelAnimation();
744 var bounds = this._windowForGesture();
745 shift = Number.constrain(shift, this._minimumBoundary - bounds.left, thi s._totalTime + this._minimumBoundary - bounds.right);
746 bounds.left += shift;
747 bounds.right += shift;
748 this._requestWindowTimes(bounds);
749 },
750
751 /**
752 * @return {{left: number, right: number}}
753 */
754 _windowForGesture: function()
755 {
756 var windowLeft = this._timeWindowLeft ? this._timeWindowLeft : this._dat aProvider.minimumBoundary();
757 var windowRight = this._timeWindowRight !== Infinity ? this._timeWindowR ight : this._dataProvider.minimumBoundary() + this._dataProvider.totalTime();
758 return {left: windowLeft, right: windowRight};
759 },
760
761 /**
762 * @param {{left: number, right: number}} bounds
763 */
764 _requestWindowTimes: function(bounds)
765 {
766 bounds.left = Number.constrain(bounds.left, this._minimumBoundary, this. _totalTime + this._minimumBoundary);
767 bounds.right = Number.constrain(bounds.right, this._minimumBoundary, thi s._totalTime + this._minimumBoundary);
768 if (bounds.right - bounds.left < WebInspector.FlameChart.MinimalTimeWind owMs)
769 return;
770 this._flameChartDelegate.requestWindowTimes(bounds.left, bounds.right);
771 },
772
773 /**
774 * @param {number} x
775 * @return {number}
776 */
777 _cursorTime: function(x)
778 {
779 return (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTi me + this._minimumBoundary;
780 },
781
782 /**
783 * @param {number} x
784 * @param {number} y
785 * @return {number}
786 */
787 _coordinatesToEntryIndex: function(x, y)
788 {
789 y += this._scrollTop;
790 var timelineData = this._timelineData();
791 if (!timelineData)
792 return -1;
793 var cursorTime = this._cursorTime(x);
794 var cursorLevel;
795 var offsetFromLevel;
796 if (this._isTopDown) {
797 cursorLevel = Math.floor((y - WebInspector.FlameChart.DividersBarHei ght) / this._barHeight);
798 offsetFromLevel = y - WebInspector.FlameChart.DividersBarHeight - cu rsorLevel * this._barHeight;
799 } else {
800 cursorLevel = Math.floor((this._canvas.height / window.devicePixelRa tio - y) / this._barHeight);
801 offsetFromLevel = this._canvas.height / window.devicePixelRatio - cu rsorLevel * this._barHeight;
802 }
803 var entryStartTimes = timelineData.entryStartTimes;
804 var entryTotalTimes = timelineData.entryTotalTimes;
805 var entryIndexes = this._timelineLevels[cursorLevel];
806 if (!entryIndexes || !entryIndexes.length)
807 return -1;
808
809 /**
810 * @param {number} time
811 * @param {number} entryIndex
812 * @return {number}
813 */
814 function comparator(time, entryIndex)
815 {
816 return time - entryStartTimes[entryIndex];
817 }
818 var indexOnLevel = Math.max(entryIndexes.upperBound(cursorTime, comparat or) - 1, 0);
819
820 /**
821 * @this {WebInspector.FlameChart}
822 * @param {number} entryIndex
823 * @return {boolean}
824 */
825 function checkEntryHit(entryIndex)
826 {
827 if (entryIndex === undefined)
828 return false;
829 var startTime = entryStartTimes[entryIndex];
830 var duration = entryTotalTimes[entryIndex];
831 if (isNaN(duration)) {
832 var dx = (startTime - cursorTime) / this._pixelToTime;
833 var dy = this._barHeight / 2 - offsetFromLevel;
834 return dx * dx + dy * dy < this._markerRadius * this._markerRadi us;
835 }
836 var endTime = startTime + duration;
837 var barThreshold = 3 * this._pixelToTime;
838 return startTime - barThreshold < cursorTime && cursorTime < endTime + barThreshold;
839 }
840
841 var entryIndex = entryIndexes[indexOnLevel];
842 if (checkEntryHit.call(this, entryIndex))
843 return entryIndex;
844 entryIndex = entryIndexes[indexOnLevel + 1];
845 if (checkEntryHit.call(this, entryIndex))
846 return entryIndex;
847 return -1;
848 },
849
850 /**
851 * @param {number} x
852 * @return {number}
853 */
854 _markerIndexAtPosition: function(x)
855 {
856 var markers = this._timelineData().markerTimestamps;
857 if (!markers)
858 return -1;
859 var accurracyOffsetPx = 1;
860 var time = this._cursorTime(x);
861 var leftTime = this._cursorTime(x - accurracyOffsetPx);
862 var rightTime = this._cursorTime(x + accurracyOffsetPx);
863
864 /**
865 * @param {number} time
866 * @param {number} markerTimestamp
867 * @return {number}
868 */
869 function comparator(time, markerTimestamp)
870 {
871 return time - markerTimestamp;
872 }
873 var left = markers.lowerBound(leftTime, comparator);
874 var markerIndex = -1;
875 var distance = Infinity;
876 for (var i = left; i < markers.length && markers[i] < rightTime; i++) {
877 var nextDistance = Math.abs(markers[i] - time);
878 if (nextDistance < distance) {
879 markerIndex = i;
880 distance = nextDistance;
881 }
882 }
883 return markerIndex;
884 },
885
886 /**
887 * @param {number} height
888 * @param {number} width
889 */
890 _draw: function(width, height)
891 {
892 var timelineData = this._timelineData();
893 if (!timelineData)
894 return;
895
896 var context = this._canvas.getContext("2d");
897 context.save();
898 var ratio = window.devicePixelRatio;
899 context.scale(ratio, ratio);
900
901 var timeWindowRight = this._timeWindowRight;
902 var timeWindowLeft = this._timeWindowLeft;
903 var timeToPixel = this._timeToPixel;
904 var pixelWindowLeft = this._pixelWindowLeft;
905 var paddingLeft = this._paddingLeft;
906 var minWidth = this._minWidth;
907 var entryTotalTimes = timelineData.entryTotalTimes;
908 var entryStartTimes = timelineData.entryStartTimes;
909 var entryLevels = timelineData.entryLevels;
910
911 var titleIndices = new Uint32Array(timelineData.entryTotalTimes);
912 var nextTitleIndex = 0;
913 var markerIndices = new Uint32Array(timelineData.entryTotalTimes);
914 var nextMarkerIndex = 0;
915 var textPadding = this._dataProvider.textPadding();
916 this._minTextWidth = 2 * textPadding + this._measureWidth(context, "\u20 26");
917 var minTextWidth = this._minTextWidth;
918
919 var barHeight = this._barHeight;
920
921 var timeToPosition = this._timeToPosition.bind(this);
922 var textBaseHeight = this._baseHeight + barHeight - this._dataProvider.t extBaseline();
923 var colorBuckets = {};
924 var minVisibleBarLevel = Math.max(Math.floor((this._scrollTop - this._ba seHeight) / barHeight), 0);
925 var maxVisibleBarLevel = Math.min(Math.floor((this._scrollTop - this._ba seHeight + height) / barHeight), this._dataProvider.maxStackDepth());
926
927 context.translate(0, -this._scrollTop);
928
929 function comparator(time, entryIndex)
930 {
931 return time - entryStartTimes[entryIndex];
932 }
933
934 for (var level = minVisibleBarLevel; level <= maxVisibleBarLevel; ++leve l) {
935 // Entries are ordered by start time within a level, so find the las t visible entry.
936 var levelIndexes = this._timelineLevels[level];
937 var rightIndexOnLevel = levelIndexes.lowerBound(timeWindowRight, com parator) - 1;
938 var lastDrawOffset = Infinity;
939 for (var entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
940 var entryIndex = levelIndexes[entryIndexOnLevel];
941 var entryStartTime = entryStartTimes[entryIndex];
942 var entryOffsetRight = entryStartTime + (isNaN(entryTotalTimes[e ntryIndex]) ? 0 : entryTotalTimes[entryIndex]);
943 if (entryOffsetRight <= timeWindowLeft)
944 break;
945
946 var barX = this._timeToPosition(entryStartTime);
947 if (barX >= lastDrawOffset)
948 continue;
949 var barRight = Math.min(this._timeToPosition(entryOffsetRight), lastDrawOffset);
950 lastDrawOffset = barX;
951
952 var color = this._dataProvider.entryColor(entryIndex);
953 var bucket = colorBuckets[color];
954 if (!bucket) {
955 bucket = [];
956 colorBuckets[color] = bucket;
957 }
958 bucket.push(entryIndex);
959 }
960 }
961
962 var colors = Object.keys(colorBuckets);
963 // We don't use for-in here because it couldn't be optimized.
964 for (var c = 0; c < colors.length; ++c) {
965 var color = colors[c];
966 context.fillStyle = color;
967 context.strokeStyle = color;
968 var indexes = colorBuckets[color];
969
970 // First fill the boxes.
971 context.beginPath();
972 for (var i = 0; i < indexes.length; ++i) {
973 var entryIndex = indexes[i];
974 var entryStartTime = entryStartTimes[entryIndex];
975 var barX = this._timeToPosition(entryStartTime);
976 var barRight = this._timeToPosition(entryStartTime + entryTotalT imes[entryIndex]);
977 var barWidth = Math.max(barRight - barX, minWidth);
978 var barLevel = entryLevels[entryIndex];
979 var barY = this._levelToHeight(barLevel);
980 if (isNaN(entryTotalTimes[entryIndex])) {
981 context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
982 context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math.PI * 2);
983 markerIndices[nextMarkerIndex++] = entryIndex;
984 } else {
985 context.rect(barX, barY, barWidth, barHeight);
986 if (barWidth > minTextWidth || this._dataProvider.forceDecor ation(entryIndex))
987 titleIndices[nextTitleIndex++] = entryIndex;
988 }
989 }
990 context.fill();
991 }
992
993 context.strokeStyle = "rgb(0, 0, 0)";
994 context.beginPath();
995 for (var m = 0; m < nextMarkerIndex; ++m) {
996 var entryIndex = markerIndices[m];
997 var entryStartTime = entryStartTimes[entryIndex];
998 var barX = this._timeToPosition(entryStartTime);
999 var barLevel = entryLevels[entryIndex];
1000 var barY = this._levelToHeight(barLevel);
1001 context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
1002 context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math. PI * 2);
1003 }
1004 context.stroke();
1005
1006 context.textBaseline = "alphabetic";
1007
1008 for (var i = 0; i < nextTitleIndex; ++i) {
1009 var entryIndex = titleIndices[i];
1010 var entryStartTime = entryStartTimes[entryIndex];
1011 var barX = this._timeToPosition(entryStartTime);
1012 var barRight = this._timeToPosition(entryStartTime + entryTotalTimes [entryIndex]);
1013 var barWidth = Math.max(barRight - barX, minWidth);
1014 var barLevel = entryLevels[entryIndex];
1015 var barY = this._levelToHeight(barLevel);
1016 var text = this._dataProvider.entryTitle(entryIndex);
1017 if (text && text.length) {
1018 context.font = this._dataProvider.entryFont(entryIndex);
1019 text = this._prepareText(context, text, barWidth - 2 * textPaddi ng);
1020 }
1021
1022 if (this._dataProvider.decorateEntry(entryIndex, context, text, barX , barY, barWidth, barHeight, timeToPosition))
1023 continue;
1024 if (!text || !text.length)
1025 continue;
1026
1027 context.fillStyle = this._dataProvider.textColor(entryIndex);
1028 context.fillText(text, barX + textPadding, textBaseHeight - barLevel * this._barHeightDelta);
1029 }
1030 context.restore();
1031
1032 var offsets = this._dataProvider.dividerOffsets(this._calculator.minimum Boundary(), this._calculator.maximumBoundary());
1033 WebInspector.TimelineGrid.drawCanvasGrid(this._canvas, this._calculator, offsets);
1034 this._drawMarkers();
1035
1036 this._updateElementPosition(this._highlightElement, this._highlightedEnt ryIndex);
1037 this._updateElementPosition(this._selectedElement, this._selectedEntryIn dex);
1038 this._updateMarkerHighlight();
1039 },
1040
1041 _drawMarkers: function()
1042 {
1043 var markerTimestamps = this._timelineData().markerTimestamps;
1044 /**
1045 * @param {number} time
1046 * @param {number} markerTimestamp
1047 * @return {number}
1048 */
1049 function compare(time, markerTimestamp)
1050 {
1051 return time - markerTimestamp;
1052 }
1053 var left = markerTimestamps.lowerBound(this._calculator.minimumBoundary( ), compare);
1054 var rightBoundary = this._calculator.maximumBoundary();
1055
1056 var context = this._canvas.getContext("2d");
1057 context.save();
1058 var ratio = window.devicePixelRatio;
1059 context.scale(ratio, ratio);
1060 var height = WebInspector.FlameChart.DividersBarHeight - 1;
1061 context.lineWidth = 2;
1062 for (var i = left; i < markerTimestamps.length; i++) {
1063 var timestamp = markerTimestamps[i];
1064 if (timestamp > rightBoundary)
1065 break;
1066 var position = this._calculator.computePosition(timestamp);
1067 context.strokeStyle = this._dataProvider.markerColor(i);
1068 context.beginPath();
1069 context.moveTo(position, 0);
1070 context.lineTo(position, height);
1071 context.stroke();
1072 if (this._dataProvider.isTallMarker(i)) {
1073 context.save()
1074 context.lineWidth = 0.5;
1075 context.translate(0.5, 0.5);
1076 context.beginPath();
1077 context.moveTo(position, height);
1078 context.setLineDash([10, 5]);
1079 context.lineTo(position, this._canvas.height);
1080 context.stroke();
1081 context.restore();
1082 }
1083 }
1084 context.restore();
1085 },
1086
1087 _updateMarkerHighlight: function()
1088 {
1089 var element = this._markerHighlighElement;
1090 if (element.parentElement)
1091 element.remove();
1092 var markerIndex = this._highlightedMarkerIndex;
1093 if (markerIndex === -1)
1094 return;
1095 var barX = this._timeToPosition(this._timelineData().markerTimestamps[ma rkerIndex]);
1096 element.title = this._dataProvider.markerTitle(markerIndex);
1097 var style = element.style;
1098 style.left = barX + "px";
1099 style.backgroundColor = this._dataProvider.markerColor(markerIndex);
1100 this.contentElement.appendChild(element);
1101 },
1102
1103 /**
1104 * @param {?WebInspector.FlameChart.TimelineData} timelineData
1105 */
1106 _processTimelineData: function(timelineData)
1107 {
1108 if (!timelineData) {
1109 this._timelineLevels = null;
1110 this._rawTimelineData = null;
1111 this._rawTimelineDataLength = 0;
1112 return;
1113 }
1114
1115 var entryCounters = new Uint32Array(this._dataProvider.maxStackDepth() + 1);
1116 for (var i = 0; i < timelineData.entryLevels.length; ++i)
1117 ++entryCounters[timelineData.entryLevels[i]];
1118 var levelIndexes = new Array(entryCounters.length);
1119 for (var i = 0; i < levelIndexes.length; ++i) {
1120 levelIndexes[i] = new Uint32Array(entryCounters[i]);
1121 entryCounters[i] = 0;
1122 }
1123 for (var i = 0; i < timelineData.entryLevels.length; ++i) {
1124 var level = timelineData.entryLevels[i];
1125 levelIndexes[level][entryCounters[level]++] = i;
1126 }
1127 this._timelineLevels = levelIndexes;
1128 this._rawTimelineData = timelineData;
1129 this._rawTimelineDataLength = timelineData.entryStartTimes.length;
1130 },
1131
1132 /**
1133 * @param {number} entryIndex
1134 */
1135 setSelectedEntry: function(entryIndex)
1136 {
1137 this._selectedEntryIndex = entryIndex;
1138 this._revealEntry(entryIndex);
1139 this._updateElementPosition(this._selectedElement, this._selectedEntryIn dex);
1140 },
1141
1142 _updateElementPosition: function(element, entryIndex)
1143 {
1144 if (element.parentElement)
1145 element.remove();
1146 if (entryIndex === -1)
1147 return;
1148 var timeRange = this._dataProvider.highlightTimeRange(entryIndex);
1149 if (!timeRange)
1150 return;
1151 var timelineData = this._timelineData();
1152 var barX = this._timeToPosition(timeRange.startTime);
1153 var barRight = this._timeToPosition(timeRange.endTime);
1154 if (barRight === 0 || barX === this._canvas.width)
1155 return;
1156 var barWidth = Math.max(barRight - barX, this._minWidth);
1157 var barY = this._levelToHeight(timelineData.entryLevels[entryIndex]) - t his._scrollTop;
1158 var style = element.style;
1159 style.left = barX + "px";
1160 style.top = barY + "px";
1161 style.width = barWidth + "px";
1162 style.height = this._barHeight + "px";
1163 this.contentElement.appendChild(element);
1164 },
1165
1166 /**
1167 * @param {number} time
1168 */
1169 _timeToPosition: function(time)
1170 {
1171 var value = Math.floor((time - this._minimumBoundary) * this._timeToPixe l) - this._pixelWindowLeft + this._paddingLeft;
1172 return Math.min(this._canvas.width, Math.max(0, value));
1173 },
1174
1175 _levelToHeight: function(level)
1176 {
1177 return this._baseHeight - level * this._barHeightDelta;
1178 },
1179
1180 _buildEntryInfo: function(entryInfo)
1181 {
1182 var infoTable = createElementWithClass("table", "info-table");
1183 for (var i = 0; i < entryInfo.length; ++i) {
1184 var row = infoTable.createChild("tr");
1185 row.createChild("td", "title").textContent = entryInfo[i].title;
1186 row.createChild("td").textContent = entryInfo[i].text;
1187 }
1188 return infoTable;
1189 },
1190
1191 /**
1192 * @param {!CanvasRenderingContext2D} context
1193 * @param {string} title
1194 * @param {number} maxSize
1195 * @return {string}
1196 */
1197 _prepareText: function(context, title, maxSize)
1198 {
1199 var titleWidth = this._measureWidth(context, title);
1200 if (maxSize >= titleWidth)
1201 return title;
1202
1203 var l = 2;
1204 var r = title.length;
1205 while (l < r) {
1206 var m = (l + r) >> 1;
1207 if (this._measureWidth(context, title.trimMiddle(m)) <= maxSize)
1208 l = m + 1;
1209 else
1210 r = m;
1211 }
1212 title = title.trimMiddle(r - 1);
1213 return title !== "\u2026" ? title : "";
1214 },
1215
1216 /**
1217 * @param {!CanvasRenderingContext2D} context
1218 * @param {string} text
1219 * @return {number}
1220 */
1221 _measureWidth: function(context, text)
1222 {
1223 if (text.length > 20)
1224 return context.measureText(text).width;
1225
1226 var font = context.font;
1227 var textWidths = this._textWidth[font];
1228 if (!textWidths) {
1229 textWidths = {};
1230 this._textWidth[font] = textWidths;
1231 }
1232 var width = textWidths[text];
1233 if (!width) {
1234 width = context.measureText(text).width;
1235 textWidths[text] = width;
1236 }
1237 return width;
1238 },
1239
1240 _updateBoundaries: function()
1241 {
1242 this._totalTime = this._dataProvider.totalTime();
1243 this._minimumBoundary = this._dataProvider.minimumBoundary();
1244
1245 if (this._timeWindowRight !== Infinity) {
1246 this._windowLeft = (this._timeWindowLeft - this._minimumBoundary) / this._totalTime;
1247 this._windowRight = (this._timeWindowRight - this._minimumBoundary) / this._totalTime;
1248 this._windowWidth = this._windowRight - this._windowLeft;
1249 } else {
1250 this._windowLeft = 0;
1251 this._windowRight = 1;
1252 this._windowWidth = 1;
1253 }
1254
1255 this._pixelWindowWidth = this._offsetWidth - this._paddingLeft;
1256 this._totalPixels = Math.floor(this._pixelWindowWidth / this._windowWidt h);
1257 this._pixelWindowLeft = Math.floor(this._totalPixels * this._windowLeft) ;
1258 this._pixelWindowRight = Math.floor(this._totalPixels * this._windowRigh t);
1259
1260 this._timeToPixel = this._totalPixels / this._totalTime;
1261 this._pixelToTime = this._totalTime / this._totalPixels;
1262 this._paddingLeftTime = this._paddingLeft / this._timeToPixel;
1263
1264 this._baseHeight = this._isTopDown ? WebInspector.FlameChart.DividersBar Height : this._offsetHeight - this._barHeight;
1265
1266 this._totalHeight = this._levelToHeight(this._dataProvider.maxStackDepth () + 1);
1267 this._vScrollContent.style.height = this._totalHeight + "px";
1268 this._scrollTop = this._vScrollElement.scrollTop;
1269 this._updateScrollBar();
1270 },
1271
1272 onResize: function()
1273 {
1274 this._updateScrollBar();
1275 this.scheduleUpdate();
1276 },
1277
1278 _updateScrollBar: function()
1279 {
1280 var showScroll = this._totalHeight > this._offsetHeight;
1281 this._vScrollElement.classList.toggle("hidden", !showScroll);
1282 this._offsetWidth = this.contentElement.offsetWidth - (WebInspector.isMa c() ? 0 : this._vScrollElement.offsetWidth);
1283 this._offsetHeight = this.contentElement.offsetHeight;
1284 },
1285
1286 scheduleUpdate: function()
1287 {
1288 if (this._updateTimerId || this._cancelWindowTimesAnimation)
1289 return;
1290 this._updateTimerId = requestAnimationFrame(this.update.bind(this));
1291 },
1292
1293 update: function()
1294 {
1295 this._updateTimerId = 0;
1296 if (!this._timelineData())
1297 return;
1298 this._resetCanvas();
1299 this._updateBoundaries();
1300 this._calculator._updateBoundaries(this);
1301 this._draw(this._offsetWidth, this._offsetHeight);
1302 },
1303
1304 reset: function()
1305 {
1306 this._highlightedMarkerIndex = -1;
1307 this._highlightedEntryIndex = -1;
1308 this._selectedEntryIndex = -1;
1309 this._textWidth = {};
1310 this.update();
1311 },
1312
1313 _enabled: function()
1314 {
1315 return this._rawTimelineDataLength !== 0;
1316 },
1317
1318 __proto__: WebInspector.HBox.prototype
1319 }
OLDNEW
« no previous file with comments | « Source/devtools/front_end/components/FilterSuggestionBuilder.js ('k') | Source/devtools/front_end/components/HelpScreen.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698