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