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._eventDividers = []; 44 45 this._model = model; 46 47 this._overviewGrid = new WebInspector.OverviewGrid("timeline"); 48 this.element.appendChild(this._overviewGrid.element); 49 50 this._overviewCalculator = new WebInspector.TimelineOverviewCalculator(); 51 52 model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._reset, this); 53 this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); 54 this._overviewControls = []; 55 } 56 57 WebInspector.TimelineOverviewPane.Events = { 58 WindowChanged: "WindowChanged" 59 }; 60 61 WebInspector.TimelineOverviewPane.prototype = { 62 wasShown: function() 63 { 64 this.update(); 65 }, 66 67 onResize: function() 68 { 69 this.update(); 70 }, 71 72 /** 73 * @param {!Array.<!WebInspector.TimelineOverview>} overviewControls 74 */ 75 setOverviewControls: function(overviewControls) 76 { 77 for (var i = 0; i < this._overviewControls.length; ++i) { 78 var overviewControl = this._overviewControls[i]; 79 overviewControl.detach(); 80 overviewControl.dispose(); 81 } 82 83 for (var i = 0; i < overviewControls.length; ++i) { 84 overviewControls[i].setOverviewGrid(this._overviewGrid); 85 overviewControls[i].show(this._overviewGrid.element); 86 } 87 this._overviewControls = overviewControls; 88 this.update(); 89 }, 90 91 update: function() 92 { 93 delete this._refreshTimeout; 94 95 this._overviewCalculator._setWindow(this._model.minimumRecordTime(), this._model.maximumRecordTime()); 96 this._overviewCalculator._setDisplayWindow(0, this._overviewGrid.clientWidth()); 97 for (var i = 0; i < this._overviewControls.length; ++i) 98 this._overviewControls[i].update(); 99 this._overviewGrid.updateDividers(this._overviewCalculator); 100 this._updateEventDividers(); 101 this._updateWindow(); 102 }, 103 104 _updateEventDividers: function() 105 { 106 var records = this._eventDividers; 107 this._overviewGrid.removeEventDividers(); 108 var dividers = []; 109 for (var i = 0; i < records.length; ++i) { 110 var record = records[i]; 111 var positions = this._overviewCalculator.computeBarGraphPercentages(record); 112 var dividerPosition = Math.round(positions.start * 10); 113 if (dividers[dividerPosition]) 114 continue; 115 var divider = this._uiUtils.createEventDivider(record.type()); 116 divider.style.left = positions.start + "%"; 117 dividers[dividerPosition] = divider; 118 } 119 this._overviewGrid.addEventDividers(dividers); 120 }, 121 122 /** 123 * @param {!WebInspector.TimelineModel.Record} record 124 */ 125 addRecord: function(record) 126 { 127 var eventDividers = this._eventDividers; 128 var uiUtils = this._uiUtils; 129 function addEventDividers(record) 130 { 131 if (uiUtils.isEventDivider(record)) 132 eventDividers.push(record); 133 } 134 WebInspector.TimelineModel.forAllRecords([record], addEventDividers); 135 this._scheduleRefresh(); 136 }, 137 138 _reset: function() 139 { 140 this._overviewCalculator.reset(); 141 this._overviewGrid.reset(); 142 this._overviewGrid.setResizeEnabled(false); 143 this._eventDividers = []; 144 this._overviewGrid.updateDividers(this._overviewCalculator); 145 for (var i = 0; i < this._overviewControls.length; ++i) 146 this._overviewControls[i].reset(); 147 this.update(); 148 }, 149 150 /** 151 * @param {!WebInspector.Event} event 152 */ 153 _onWindowChanged: function(event) 154 { 155 if (this._muteOnWindowChanged) 156 return; 157 // Always use first control as a time converter. 158 if (!this._overviewControls.length) 159 return; 160 var windowTimes = this._overviewControls[0].windowTimes(this._overviewGrid.windowLeft(), this._overviewGrid.windowRight()); 161 this._windowStartTime = windowTimes.startTime; 162 this._windowEndTime = windowTimes.endTime; 163 this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged, windowTimes); 164 }, 165 166 /** 167 * @param {number} startTime 168 * @param {number} endTime 169 */ 170 requestWindowTimes: function(startTime, endTime) 171 { 172 if (startTime === this._windowStartTime && endTime === this._windowEndTime) 173 return; 174 this._windowStartTime = startTime; 175 this._windowEndTime = endTime; 176 this._updateWindow(); 177 this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged, { startTime: startTime, endTime: endTime }); 178 }, 179 180 _updateWindow: function() 181 { 182 if (!this._overviewControls.length) 183 return; 184 var windowBoundaries = this._overviewControls[0].windowBoundaries(this._windowStartTime, this._windowEndTime); 185 this._muteOnWindowChanged = true; 186 this._overviewGrid.setWindow(windowBoundaries.left, windowBoundaries.right); 187 this._overviewGrid.setResizeEnabled(!!this._model.records().length); 188 this._muteOnWindowChanged = false; 189 }, 190 191 _scheduleRefresh: function() 192 { 193 if (this._refreshTimeout) 194 return; 195 if (!this.isShowing()) 196 return; 197 this._refreshTimeout = setTimeout(this.update.bind(this), 300); 198 }, 199 200 __proto__: WebInspector.VBox.prototype 201 } 202 203 /** 204 * @constructor 205 * @implements {WebInspector.TimelineGrid.Calculator} 206 */ 207 WebInspector.TimelineOverviewCalculator = function() 208 { 209 } 210 211 WebInspector.TimelineOverviewCalculator.prototype = { 212 /** 213 * @return {number} 214 */ 215 paddingLeft: function() 216 { 217 return this._paddingLeft; 218 }, 219 220 /** 221 * @param {number} time 222 * @return {number} 223 */ 224 computePosition: function(time) 225 { 226 return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this._paddingLeft; 227 }, 228 229 /** 230 * @return {!{start: number, end: number}} 231 */ 232 computeBarGraphPercentages: function(record) 233 { 234 var start = (record.startTime() - this._minimumBoundary) / this.boundarySpan() * 100; 235 var end = (record.endTime() - this._minimumBoundary) / this.boundarySpan() * 100; 236 return {start: start, end: end}; 237 }, 238 239 /** 240 * @param {number=} minimumRecordTime 241 * @param {number=} maximumRecordTime 242 */ 243 _setWindow: function(minimumRecordTime, maximumRecordTime) 244 { 245 this._minimumBoundary = minimumRecordTime; 246 this._maximumBoundary = maximumRecordTime; 247 }, 248 249 /** 250 * @param {number} paddingLeft 251 * @param {number} clientWidth 252 */ 253 _setDisplayWindow: function(paddingLeft, clientWidth) 254 { 255 this._workingArea = clientWidth - paddingLeft; 256 this._paddingLeft = paddingLeft; 257 }, 258 259 reset: function() 260 { 261 this._setWindow(0, 1000); 262 }, 263 264 /** 265 * @param {number} value 266 * @param {number=} precision 267 * @return {string} 268 */ 269 formatTime: function(value, precision) 270 { 271 return Number.preciseMillisToString(value - this.zeroTime(), precision); 272 }, 273 274 /** 275 * @return {number} 276 */ 277 maximumBoundary: function() 278 { 279 return this._maximumBoundary; 280 }, 281 282 /** 283 * @return {number} 284 */ 285 minimumBoundary: function() 286 { 287 return this._minimumBoundary; 288 }, 289 290 /** 291 * @return {number} 292 */ 293 zeroTime: function() 294 { 295 return this._minimumBoundary; 296 }, 297 298 /** 299 * @return {number} 300 */ 301 boundarySpan: function() 302 { 303 return this._maximumBoundary - this._minimumBoundary; 304 } 305 } 306 307 /** 308 * @interface 309 */ 310 WebInspector.TimelineOverview = function(model) 311 { 312 } 313 314 WebInspector.TimelineOverview.prototype = { 315 /** 316 * @param {?Element} parentElement 317 * @param {!Element=} insertBefore 318 */ 319 show: function(parentElement, insertBefore) { }, 320 321 /** 322 * @param {!WebInspector.OverviewGrid} grid 323 */ 324 setOverviewGrid: function(grid) { }, 325 326 update: function() { }, 327 328 dispose: function() { }, 329 330 reset: function() { }, 331 332 /** 333 * @param {number} windowLeft 334 * @param {number} windowRight 335 * @return {!{startTime: number, endTime: number}} 336 */ 337 windowTimes: function(windowLeft, windowRight) { }, 338 339 /** 340 * @param {number} startTime 341 * @param {number} endTime 342 * @return {!{left: number, right: number}} 343 */ 344 windowBoundaries: function(startTime, endTime) { }, 345 } 346 347 /** 348 * @constructor 349 * @extends {WebInspector.VBox} 350 * @implements {WebInspector.TimelineOverview} 351 * @param {!WebInspector.TimelineModel} model 352 */ 353 WebInspector.TimelineOverviewBase = function(model) 354 { 355 WebInspector.VBox.call(this); 356 357 this._model = model; 358 this._canvas = this.element.createChild("canvas", "fill"); 359 this._context = this._canvas.getContext("2d"); 360 } 361 362 WebInspector.TimelineOverviewBase.prototype = { 363 /** 364 * @param {!WebInspector.OverviewGrid} grid 365 */ 366 setOverviewGrid: function(grid) 367 { 368 }, 369 370 update: function() 371 { 372 this.resetCanvas(); 373 }, 374 375 dispose: function() 376 { 377 }, 378 379 reset: function() 380 { 381 }, 382 383 timelineStarted: function() 384 { 385 }, 386 387 timelineStopped: function() 388 { 389 }, 390 391 /** 392 * @param {number} windowLeft 393 * @param {number} windowRight 394 * @return {!{startTime: number, endTime: number}} 395 */ 396 windowTimes: function(windowLeft, windowRight) 397 { 398 var absoluteMin = this._model.minimumRecordTime(); 399 var timeSpan = this._model.maximumRecordTime() - absoluteMin; 400 return { 401 startTime: absoluteMin + timeSpan * windowLeft, 402 endTime: absoluteMin + timeSpan * windowRight 403 }; 404 }, 405 406 /** 407 * @param {number} startTime 408 * @param {number} endTime 409 * @return {!{left: number, right: number}} 410 */ 411 windowBoundaries: function(startTime, endTime) 412 { 413 var absoluteMin = this._model.minimumRecordTime(); 414 var timeSpan = this._model.maximumRecordTime() - absoluteMin; 415 var haveRecords = absoluteMin > 0; 416 return { 417 left: haveRecords && startTime ? Math.min((startTime - absoluteMin) / timeSpan, 1) : 0, 418 right: haveRecords && endTime < Infinity ? (endTime - absoluteMin) / timeSpan : 1 419 } 420 }, 421 422 resetCanvas: function() 423 { 424 this._canvas.width = this.element.clientWidth * window.devicePixelRatio; 425 this._canvas.height = this.element.clientHeight * window.devicePixelRatio; 426 }, 427 428 __proto__: WebInspector.VBox.prototype 429 } 430