1 /* 2 * Copyright (C) 2013 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 * @extends {WebInspector.VBox} 34 * @param {!WebInspector.TimelineModel} model 35 * @param {!WebInspector.TimelineUIUtils} uiUtils 36 */ 37 WebInspector.TimelineOverviewPane = function(model, uiUtils) 38 { 39 WebInspector.VBox.call(this); 40 this._uiUtils = uiUtils; 41 this.element.id = "timeline-overview-pane"; 42 43 this._model = model; 44 this._overviewCalculator = new WebInspector.TimelineOverviewCalculator(); 45 46 this._overviewGrid = new WebInspector.OverviewGrid("timeline"); 47 this.element.appendChild(this._overviewGrid.element); 48 49 model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._reset, this); 50 this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); 51 this._overviewControls = []; 52 } 53 54 WebInspector.TimelineOverviewPane.Events = { 55 WindowChanged: "WindowChanged" 56 }; 57 58 WebInspector.TimelineOverviewPane.prototype = { 59 wasShown: function() 60 { 61 this.update(); 62 }, 63 64 onResize: function() 65 { 66 this.update(); 67 }, 68 69 /** 70 * @param {!Array.<!WebInspector.TimelineOverview>} overviewControls 71 */ 72 setOverviewControls: function(overviewControls) 73 { 74 for (var i = 0; i < this._overviewControls.length; ++i) { 75 var overviewControl = this._overviewControls[i]; 76 overviewControl.detach(); 77 overviewControl.dispose(); 78 } 79 80 for (var i = 0; i < overviewControls.length; ++i) { 81 overviewControls[i].setOverviewGrid(this._overviewGrid); 82 overviewControls[i].show(this._overviewGrid.element); 83 } 84 this._overviewControls = overviewControls; 85 this.update(); 86 }, 87 88 update: function() 89 { 90 if (this._model.isEmpty()) 91 this._overviewCalculator._setWindow(0, 1000); 92 else 93 this._overviewCalculator._setWindow(this._model.minimumRecordTime(), this._model.maximumRecordTime()); 94 95 this._overviewCalculator._setDisplayWindow(0, this._overviewGrid.clientWidth()); 96 for (var i = 0; i < this._overviewControls.length; ++i) 97 this._overviewControls[i].update(); 98 this._overviewGrid.updateDividers(this._overviewCalculator); 99 this._updateEventDividers(); 100 this._updateWindow(); 101 }, 102 103 _updateEventDividers: function() 104 { 105 var records = this._model.eventDividerRecords(); 106 this._overviewGrid.removeEventDividers(); 107 var dividers = []; 108 for (var i = 0; i < records.length; ++i) { 109 var record = records[i]; 110 var positions = this._overviewCalculator.computeBarGraphPercentages(record); 111 var dividerPosition = Math.round(positions.start * 10); 112 if (dividers[dividerPosition]) 113 continue; 114 var title = this._uiUtils.titleForRecord(record); 115 var divider = this._uiUtils.createEventDivider(record.type(), title); 116 divider.style.left = positions.start + "%"; 117 dividers[dividerPosition] = divider; 118 } 119 this._overviewGrid.addEventDividers(dividers); 120 }, 121 122 _reset: function() 123 { 124 this._overviewCalculator.reset(); 125 this._overviewGrid.reset(); 126 this._overviewGrid.setResizeEnabled(false); 127 this._overviewGrid.updateDividers(this._overviewCalculator); 128 for (var i = 0; i < this._overviewControls.length; ++i) 129 this._overviewControls[i].reset(); 130 this.update(); 131 }, 132 133 /** 134 * @param {!WebInspector.Event} event 135 */ 136 _onWindowChanged: function(event) 137 { 138 if (this._muteOnWindowChanged) 139 return; 140 // Always use first control as a time converter. 141 if (!this._overviewControls.length) 142 return; 143 var windowTimes = this._overviewControls[0].windowTimes(this._overviewGrid.windowLeft(), this._overviewGrid.windowRight()); 144 this._windowStartTime = windowTimes.startTime; 145 this._windowEndTime = windowTimes.endTime; 146 this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged, windowTimes); 147 }, 148 149 /** 150 * @param {number} startTime 151 * @param {number} endTime 152 */ 153 requestWindowTimes: function(startTime, endTime) 154 { 155 if (startTime === this._windowStartTime && endTime === this._windowEndTime) 156 return; 157 this._windowStartTime = startTime; 158 this._windowEndTime = endTime; 159 this._updateWindow(); 160 this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged, { startTime: startTime, endTime: endTime }); 161 }, 162 163 _updateWindow: function() 164 { 165 if (!this._overviewControls.length) 166 return; 167 var windowBoundaries = this._overviewControls[0].windowBoundaries(this._windowStartTime, this._windowEndTime); 168 this._muteOnWindowChanged = true; 169 this._overviewGrid.setWindow(windowBoundaries.left, windowBoundaries.right); 170 this._overviewGrid.setResizeEnabled(!!this._model.records().length); 171 this._muteOnWindowChanged = false; 172 }, 173 174 __proto__: WebInspector.VBox.prototype 175 } 176 177 /** 178 * @constructor 179 * @implements {WebInspector.TimelineGrid.Calculator} 180 */ 181 WebInspector.TimelineOverviewCalculator = function() 182 { 183 } 184 185 WebInspector.TimelineOverviewCalculator.prototype = { 186 /** 187 * @return {number} 188 */ 189 paddingLeft: function() 190 { 191 return this._paddingLeft; 192 }, 193 194 /** 195 * @param {number} time 196 * @return {number} 197 */ 198 computePosition: function(time) 199 { 200 return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this._paddingLeft; 201 }, 202 203 /** 204 * @return {!{start: number, end: number}} 205 */ 206 computeBarGraphPercentages: function(record) 207 { 208 var start = (record.startTime() - this._minimumBoundary) / this.boundarySpan() * 100; 209 var end = (record.endTime() - this._minimumBoundary) / this.boundarySpan() * 100; 210 return {start: start, end: end}; 211 }, 212 213 /** 214 * @param {number=} minimumRecordTime 215 * @param {number=} maximumRecordTime 216 */ 217 _setWindow: function(minimumRecordTime, maximumRecordTime) 218 { 219 this._minimumBoundary = minimumRecordTime; 220 this._maximumBoundary = maximumRecordTime; 221 }, 222 223 /** 224 * @param {number} paddingLeft 225 * @param {number} clientWidth 226 */ 227 _setDisplayWindow: function(paddingLeft, clientWidth) 228 { 229 this._workingArea = clientWidth - paddingLeft; 230 this._paddingLeft = paddingLeft; 231 }, 232 233 reset: function() 234 { 235 this._setWindow(0, 1000); 236 }, 237 238 /** 239 * @param {number} value 240 * @param {number=} precision 241 * @return {string} 242 */ 243 formatTime: function(value, precision) 244 { 245 return Number.preciseMillisToString(value - this.zeroTime(), precision); 246 }, 247 248 /** 249 * @return {number} 250 */ 251 maximumBoundary: function() 252 { 253 return this._maximumBoundary; 254 }, 255 256 /** 257 * @return {number} 258 */ 259 minimumBoundary: function() 260 { 261 return this._minimumBoundary; 262 }, 263 264 /** 265 * @return {number} 266 */ 267 zeroTime: function() 268 { 269 return this._minimumBoundary; 270 }, 271 272 /** 273 * @return {number} 274 */ 275 boundarySpan: function() 276 { 277 return this._maximumBoundary - this._minimumBoundary; 278 } 279 } 280 281 /** 282 * @interface 283 */ 284 WebInspector.TimelineOverview = function(model) 285 { 286 } 287 288 WebInspector.TimelineOverview.prototype = { 289 /** 290 * @param {?Element} parentElement 291 * @param {!Element=} insertBefore 292 */ 293 show: function(parentElement, insertBefore) { }, 294 295 /** 296 * @param {!WebInspector.OverviewGrid} grid 297 */ 298 setOverviewGrid: function(grid) { }, 299 300 update: function() { }, 301 302 dispose: function() { }, 303 304 reset: function() { }, 305 306 /** 307 * @param {number} windowLeft 308 * @param {number} windowRight 309 * @return {!{startTime: number, endTime: number}} 310 */ 311 windowTimes: function(windowLeft, windowRight) { }, 312 313 /** 314 * @param {number} startTime 315 * @param {number} endTime 316 * @return {!{left: number, right: number}} 317 */ 318 windowBoundaries: function(startTime, endTime) { }, 319 } 320 321 /** 322 * @constructor 323 * @extends {WebInspector.VBox} 324 * @implements {WebInspector.TimelineOverview} 325 * @param {!WebInspector.TimelineModel} model 326 */ 327 WebInspector.TimelineOverviewBase = function(model) 328 { 329 WebInspector.VBox.call(this); 330 331 this._model = model; 332 this._canvas = this.element.createChild("canvas", "fill"); 333 this._context = this._canvas.getContext("2d"); 334 } 335 336 WebInspector.TimelineOverviewBase.prototype = { 337 /** 338 * @param {!WebInspector.OverviewGrid} grid 339 */ 340 setOverviewGrid: function(grid) 341 { 342 }, 343 344 update: function() 345 { 346 this.resetCanvas(); 347 }, 348 349 dispose: function() 350 { 351 }, 352 353 reset: function() 354 { 355 }, 356 357 timelineStarted: function() 358 { 359 }, 360 361 timelineStopped: function() 362 { 363 }, 364 365 /** 366 * @param {number} windowLeft 367 * @param {number} windowRight 368 * @return {!{startTime: number, endTime: number}} 369 */ 370 windowTimes: function(windowLeft, windowRight) 371 { 372 var absoluteMin = this._model.minimumRecordTime(); 373 var timeSpan = this._model.maximumRecordTime() - absoluteMin; 374 return { 375 startTime: absoluteMin + timeSpan * windowLeft, 376 endTime: absoluteMin + timeSpan * windowRight 377 }; 378 }, 379 380 /** 381 * @param {number} startTime 382 * @param {number} endTime 383 * @return {!{left: number, right: number}} 384 */ 385 windowBoundaries: function(startTime, endTime) 386 { 387 var absoluteMin = this._model.minimumRecordTime(); 388 var timeSpan = this._model.maximumRecordTime() - absoluteMin; 389 var haveRecords = absoluteMin > 0; 390 return { 391 left: haveRecords && startTime ? Math.min((startTime - absoluteMin) / timeSpan, 1) : 0, 392 right: haveRecords && endTime < Infinity ? (endTime - absoluteMin) / timeSpan : 1 393 } 394 }, 395 396 resetCanvas: function() 397 { 398 this._canvas.width = this.element.clientWidth * window.devicePixelRatio; 399 this._canvas.height = this.element.clientHeight * window.devicePixelRatio; 400 }, 401 402 __proto__: WebInspector.VBox.prototype 403 } 404