OLD | NEW |
| (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 } | |
OLD | NEW |