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.View} 34 * @param {!WebInspector.TimelineModel} model 35 */ 36WebInspector.TimelineOverviewPane = function(model) 37{ 38 WebInspector.View.call(this); 39 this.element.id = "timeline-overview-pane"; 40 41 this._windowStartTime = 0; 42 this._windowEndTime = Infinity; 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.RecordAdded, this._onRecordAdded, this); 53 model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._reset, this); 54 this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); 55} 56 57WebInspector.TimelineOverviewPane.Events = { 58 WindowChanged: "WindowChanged" 59}; 60 61WebInspector.TimelineOverviewPane.prototype = { 62 wasShown: function() 63 { 64 this._update(); 65 }, 66 67 onResize: function() 68 { 69 this._update(); 70 }, 71 72 /** 73 * @param {!WebInspector.TimelineOverviewBase} overviewControl 74 */ 75 willSetOverviewControl: function(overviewControl) 76 { 77 this._sameOverviewControl = this._overviewControl === overviewControl; 78 if (this._sameOverviewControl) 79 return; 80 this._windowTimes = null; 81 if (this._overviewControl) { 82 this._windowTimes = this._overviewControl.windowTimes(this.windowLeft(), this.windowRight()); 83 this._overviewControl.detach(); 84 } 85 this._overviewControl = overviewControl; 86 this._overviewControl.show(this._overviewGrid.element); 87 this._update(); 88 }, 89 90 didSetOverviewControl: function() 91 { 92 if (this._sameOverviewControl) 93 return; 94 if (this._windowTimes && this._windowTimes.startTime >= 0) 95 this.setWindowTimes(this._windowTimes.startTime, this._windowTimes.endTime); 96 this._update(); 97 }, 98 99 _update: function() 100 { 101 delete this._refreshTimeout; 102 103 this._updateWindow(); 104 this._overviewCalculator.setWindow(this._model.minimumRecordTime(), this._model.maximumRecordTime()); 105 this._overviewCalculator.setDisplayWindow(0, this._overviewGrid.clientWidth()); 106 107 this._overviewControl.update(); 108 this._overviewGrid.updateDividers(this._overviewCalculator); 109 this._updateEventDividers(); 110 }, 111 112 _updateEventDividers: function() 113 { 114 var records = this._eventDividers; 115 this._overviewGrid.removeEventDividers(); 116 var dividers = []; 117 for (var i = 0; i < records.length; ++i) { 118 var record = records[i]; 119 var positions = this._overviewCalculator.computeBarGraphPercentages(record); 120 var dividerPosition = Math.round(positions.start * 10); 121 if (dividers[dividerPosition]) 122 continue; 123 var divider = WebInspector.TimelinePresentationModel.createEventDivider(record.type); 124 divider.style.left = positions.start + "%"; 125 dividers[dividerPosition] = divider; 126 } 127 this._overviewGrid.addEventDividers(dividers); 128 }, 129 130 /** 131 * @param {!WebInspector.TimelineFrame} frame 132 */ 133 zoomToFrame: function(frame) 134 { 135 this.setWindowTimes(frame.startTime, frame.endTime); 136 this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged); 137 }, 138 139 _onRecordAdded: function(event) 140 { 141 var record = event.data; 142 var eventDividers = this._eventDividers; 143 function addEventDividers(record) 144 { 145 if (WebInspector.TimelinePresentationModel.isEventDivider(record)) 146 eventDividers.push(record); 147 } 148 WebInspector.TimelinePresentationModel.forAllRecords([record], addEventDividers); 149 this._scheduleRefresh(); 150 }, 151 152 _reset: function() 153 { 154 this._windowStartTime = 0; 155 this._windowEndTime = Infinity; 156 this._overviewCalculator.reset(); 157 this._overviewGrid.reset(); 158 this._overviewGrid.setResizeEnabled(false); 159 this._eventDividers = []; 160 this._overviewGrid.updateDividers(this._overviewCalculator); 161 this._overviewControl.reset(); 162 this._update(); 163 }, 164 165 windowStartTime: function() 166 { 167 return this._windowStartTime || this._model.minimumRecordTime(); 168 }, 169 170 windowEndTime: function() 171 { 172 return this._windowEndTime < Infinity ? this._windowEndTime : this._model.maximumRecordTime(); 173 }, 174 175 windowLeft: function() 176 { 177 return this._overviewGrid.windowLeft(); 178 }, 179 180 windowRight: function() 181 { 182 return this._overviewGrid.windowRight(); 183 }, 184 185 _onWindowChanged: function() 186 { 187 if (this._ignoreWindowChangedEvent) 188 return; 189 var times = this._overviewControl.windowTimes(this.windowLeft(), this.windowRight()); 190 this._windowStartTime = times.startTime; 191 this._windowEndTime = times.endTime; 192 this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged); 193 }, 194 195 /** 196 * @param {!Number} startTime 197 * @param {!Number} endTime 198 */ 199 setWindowTimes: function(startTime, endTime) 200 { 201 this._windowStartTime = startTime; 202 this._windowEndTime = endTime; 203 this._updateWindow(); 204 }, 205 206 _updateWindow: function() 207 { 208 var windowBoundaries = this._overviewControl.windowBoundaries(this._windowStartTime, this._windowEndTime); 209 this._ignoreWindowChangedEvent = true; 210 this._overviewGrid.setWindow(windowBoundaries.left, windowBoundaries.right); 211 this._overviewGrid.setResizeEnabled(this._model.records.length); 212 this._ignoreWindowChangedEvent = false; 213 }, 214 215 _scheduleRefresh: function() 216 { 217 if (this._refreshTimeout) 218 return; 219 if (!this.isShowing()) 220 return; 221 this._refreshTimeout = setTimeout(this._update.bind(this), 300); 222 }, 223 224 __proto__: WebInspector.View.prototype 225} 226 227/** 228 * @constructor 229 * @implements {WebInspector.TimelineGrid.Calculator} 230 */ 231WebInspector.TimelineOverviewCalculator = function() 232{ 233} 234 235WebInspector.TimelineOverviewCalculator.prototype = { 236 /** 237 * @param {number} time 238 */ 239 computePosition: function(time) 240 { 241 return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this.paddingLeft; 242 }, 243 244 computeBarGraphPercentages: function(record) 245 { 246 var start = (WebInspector.TimelineModel.startTimeInSeconds(record) - this._minimumBoundary) / this.boundarySpan() * 100; 247 var end = (WebInspector.TimelineModel.endTimeInSeconds(record) - this._minimumBoundary) / this.boundarySpan() * 100; 248 return {start: start, end: end}; 249 }, 250 251 /** 252 * @param {number=} minimum 253 * @param {number=} maximum 254 */ 255 setWindow: function(minimum, maximum) 256 { 257 this._minimumBoundary = minimum >= 0 ? minimum : undefined; 258 this._maximumBoundary = maximum >= 0 ? maximum : undefined; 259 }, 260 261 /** 262 * @param {number} paddingLeft 263 * @param {number} clientWidth 264 */ 265 setDisplayWindow: function(paddingLeft, clientWidth) 266 { 267 this._workingArea = clientWidth - paddingLeft; 268 this.paddingLeft = paddingLeft; 269 }, 270 271 reset: function() 272 { 273 this.setWindow(); 274 }, 275 276 /** 277 * @param {number} value 278 * @param {boolean=} hires 279 * @return {string} 280 */ 281 formatTime: function(value, hires) 282 { 283 return Number.secondsToString(value, hires); 284 }, 285 286 maximumBoundary: function() 287 { 288 return this._maximumBoundary; 289 }, 290 291 minimumBoundary: function() 292 { 293 return this._minimumBoundary; 294 }, 295 296 zeroTime: function() 297 { 298 return this._minimumBoundary; 299 }, 300 301 boundarySpan: function() 302 { 303 return this._maximumBoundary - this._minimumBoundary; 304 } 305} 306 307/** 308 * @constructor 309 * @extends {WebInspector.View} 310 * @param {!WebInspector.TimelineModel} model 311 */ 312WebInspector.TimelineOverviewBase = function(model) 313{ 314 WebInspector.View.call(this); 315 this.element.classList.add("fill"); 316 317 this._model = model; 318 this._canvas = this.element.createChild("canvas", "fill"); 319 this._context = this._canvas.getContext("2d"); 320} 321 322WebInspector.TimelineOverviewBase.prototype = { 323 update: function() { }, 324 reset: function() { }, 325 326 /** 327 * @param {number} windowLeft 328 * @param {number} windowRight 329 */ 330 windowTimes: function(windowLeft, windowRight) 331 { 332 var absoluteMin = this._model.minimumRecordTime(); 333 var timeSpan = this._model.maximumRecordTime() - absoluteMin; 334 return { 335 startTime: absoluteMin + timeSpan * windowLeft, 336 endTime: absoluteMin + timeSpan * windowRight 337 }; 338 }, 339 340 /** 341 * @param {number} startTime 342 * @param {number} endTime 343 */ 344 windowBoundaries: function(startTime, endTime) 345 { 346 var absoluteMin = this._model.minimumRecordTime(); 347 var timeSpan = this._model.maximumRecordTime() - absoluteMin; 348 var haveRecords = absoluteMin >= 0; 349 return { 350 left: haveRecords && startTime ? Math.min((startTime - absoluteMin) / timeSpan, 1) : 0, 351 right: haveRecords && endTime < Infinity ? (endTime - absoluteMin) / timeSpan : 1 352 } 353 }, 354 355 resetCanvas: function() 356 { 357 this._canvas.width = this.element.clientWidth * window.devicePixelRatio; 358 this._canvas.height = this.element.clientHeight * window.devicePixelRatio; 359 }, 360 361 __proto__: WebInspector.View.prototype 362} 363