| 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 |