1/* 2 * Copyright (C) 2012 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 * @param {WebInspector.TimelinePanel} timelinePanel 33 * @param {WebInspector.TimelineModel} model 34 * @param {number} sidebarWidth 35 * @constructor 36 */ 37WebInspector.MemoryStatistics = function(timelinePanel, model, sidebarWidth) 38{ 39 this._timelinePanel = timelinePanel; 40 this._counters = []; 41 42 model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this); 43 model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this); 44 45 this._containerAnchor = timelinePanel.element.lastChild; 46 this._memorySidebarView = new WebInspector.SidebarView(WebInspector.SidebarView.SidebarPosition.Start, undefined, sidebarWidth); 47 this._memorySidebarView.sidebarElement.addStyleClass("sidebar"); 48 this._memorySidebarView.element.id = "memory-graphs-container"; 49 50 this._memorySidebarView.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._sidebarResized.bind(this)); 51 52 this._canvasContainer = this._memorySidebarView.mainElement; 53 this._canvasContainer.id = "memory-graphs-canvas-container"; 54 this._createCurrentValuesBar(); 55 this._canvas = this._canvasContainer.createChild("canvas"); 56 this._canvas.id = "memory-counters-graph"; 57 this._lastMarkerXPosition = 0; 58 59 this._canvas.addEventListener("mouseover", this._onMouseOver.bind(this), true); 60 this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), true); 61 this._canvas.addEventListener("mouseout", this._onMouseOut.bind(this), true); 62 this._canvas.addEventListener("click", this._onClick.bind(this), true); 63 // We create extra timeline grid here to reuse its event dividers. 64 this._timelineGrid = new WebInspector.TimelineGrid(); 65 this._canvasContainer.appendChild(this._timelineGrid.dividersElement); 66 67 // Populate sidebar 68 this._memorySidebarView.sidebarElement.createChild("div", "sidebar-tree sidebar-tree-section").textContent = WebInspector.UIString("COUNTERS"); 69 this._counterUI = this._createCounterUIList(); 70} 71 72/** 73 * @constructor 74 * @param {number} time 75 */ 76WebInspector.MemoryStatistics.Counter = function(time) 77{ 78 this.time = time; 79} 80 81/** 82 * @constructor 83 * @extends {WebInspector.Object} 84 */ 85WebInspector.SwatchCheckbox = function(title, color) 86{ 87 this.element = document.createElement("div"); 88 this._swatch = this.element.createChild("div", "swatch"); 89 this.element.createChild("span", "title").textContent = title; 90 this._color = color; 91 this.checked = true; 92 93 this.element.addEventListener("click", this._toggleCheckbox.bind(this), true); 94} 95 96WebInspector.SwatchCheckbox.Events = { 97 Changed: "Changed" 98} 99 100WebInspector.SwatchCheckbox.prototype = { 101 get checked() 102 { 103 return this._checked; 104 }, 105 106 set checked(v) 107 { 108 this._checked = v; 109 if (this._checked) 110 this._swatch.style.backgroundColor = this._color; 111 else 112 this._swatch.style.backgroundColor = ""; 113 }, 114 115 _toggleCheckbox: function(event) 116 { 117 this.checked = !this.checked; 118 this.dispatchEventToListeners(WebInspector.SwatchCheckbox.Events.Changed); 119 }, 120 121 __proto__: WebInspector.Object.prototype 122} 123 124/** 125 * @constructor 126 */ 127WebInspector.CounterUIBase = function(memoryCountersPane, title, graphColor, valueGetter) 128{ 129 this._memoryCountersPane = memoryCountersPane; 130 this.valueGetter = valueGetter; 131 var container = memoryCountersPane._memorySidebarView.sidebarElement.createChild("div", "memory-counter-sidebar-info"); 132 var swatchColor = graphColor; 133 this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title), swatchColor); 134 this._swatch.addEventListener(WebInspector.SwatchCheckbox.Events.Changed, this._toggleCounterGraph.bind(this)); 135 container.appendChild(this._swatch.element); 136 137 this._value = null; 138 this.graphColor =graphColor; 139 this.strokeColor = graphColor; 140 this.graphYValues = []; 141} 142 143WebInspector.CounterUIBase.prototype = { 144 _toggleCounterGraph: function(event) 145 { 146 if (this._swatch.checked) 147 this._value.removeStyleClass("hidden"); 148 else 149 this._value.addStyleClass("hidden"); 150 this._memoryCountersPane.refresh(); 151 }, 152 153 updateCurrentValue: function(countersEntry) 154 { 155 this._value.textContent = Number.bytesToString(this.valueGetter(countersEntry)); 156 }, 157 158 clearCurrentValueAndMarker: function(ctx) 159 { 160 this._value.textContent = ""; 161 }, 162 163 get visible() 164 { 165 return this._swatch.checked; 166 }, 167} 168 169WebInspector.MemoryStatistics.prototype = { 170 _createCurrentValuesBar: function() 171 { 172 throw new Error("Not implemented"); 173 }, 174 175 _createCounterUIList: function() 176 { 177 throw new Error("Not implemented"); 178 }, 179 180 _onRecordsCleared: function() 181 { 182 this._counters = []; 183 }, 184 185 /** 186 * @param {WebInspector.TimelineGrid} timelineGrid 187 */ 188 setMainTimelineGrid: function(timelineGrid) 189 { 190 this._mainTimelineGrid = timelineGrid; 191 }, 192 193 /** 194 * @param {number} top 195 */ 196 setTopPosition: function(top) 197 { 198 this._memorySidebarView.element.style.top = top + "px"; 199 this._updateSize(); 200 }, 201 202 /** 203 * @param {number} width 204 */ 205 setSidebarWidth: function(width) 206 { 207 if (this._ignoreSidebarResize) 208 return; 209 this._ignoreSidebarResize = true; 210 this._memorySidebarView.setSidebarWidth(width); 211 this._ignoreSidebarResize = false; 212 }, 213 214 /** 215 * @param {WebInspector.Event} event 216 */ 217 _sidebarResized: function(event) 218 { 219 if (this._ignoreSidebarResize) 220 return; 221 this._ignoreSidebarResize = true; 222 this._timelinePanel.splitView.setSidebarWidth(event.data); 223 this._ignoreSidebarResize = false; 224 }, 225 226 _canvasHeight: function() 227 { 228 throw new Error("Not implemented"); 229 }, 230 231 _updateSize: function() 232 { 233 var width = this._mainTimelineGrid.dividersElement.offsetWidth + 1; 234 this._canvasContainer.style.width = width + "px"; 235 236 var height = this._canvasHeight(); 237 this._canvas.width = width; 238 this._canvas.height = height; 239 }, 240 241 /** 242 * @param {WebInspector.Event} event 243 */ 244 _onRecordAdded: function(event) 245 { 246 throw new Error("Not implemented"); 247 }, 248 249 _draw: function() 250 { 251 this._calculateVisibleIndexes(); 252 this._calculateXValues(); 253 this._clear(); 254 255 this._setVerticalClip(10, this._canvas.height - 20); 256 }, 257 258 _calculateVisibleIndexes: function() 259 { 260 var calculator = this._timelinePanel.calculator; 261 var start = calculator.minimumBoundary() * 1000; 262 var end = calculator.maximumBoundary() * 1000; 263 var firstIndex = 0; 264 var lastIndex = this._counters.length - 1; 265 for (var i = 0; i < this._counters.length; i++) { 266 var time = this._counters[i].time; 267 if (time <= start) { 268 firstIndex = i; 269 } else { 270 if (end < time) 271 break; 272 lastIndex = i; 273 } 274 } 275 // Maximum index of element whose time <= start. 276 this._minimumIndex = firstIndex; 277 278 // Maximum index of element whose time <= end. 279 this._maximumIndex = lastIndex; 280 281 // Current window bounds. 282 this._minTime = start; 283 this._maxTime = end; 284 }, 285 286 /** 287 * @param {MouseEvent} event 288 */ 289 _onClick: function(event) 290 { 291 var x = event.x - event.target.offsetParent.offsetLeft; 292 var i = this._recordIndexAt(x); 293 var counter = this._counters[i]; 294 if (counter) 295 this._timelinePanel.revealRecordAt(counter.time / 1000); 296 }, 297 298 /** 299 * @param {MouseEvent} event 300 */ 301 _onMouseOut: function(event) 302 { 303 delete this._markerXPosition; 304 305 var ctx = this._canvas.getContext("2d"); 306 this._clearCurrentValueAndMarker(ctx); 307 }, 308 309 /** 310 * @param {CanvasRenderingContext2D} ctx 311 */ 312 _clearCurrentValueAndMarker: function(ctx) 313 { 314 for (var i = 0; i < this._counterUI.length; i++) 315 this._counterUI[i].clearCurrentValueAndMarker(ctx); 316 }, 317 318 /** 319 * @param {MouseEvent} event 320 */ 321 _onMouseOver: function(event) 322 { 323 this._onMouseMove(event); 324 }, 325 326 /** 327 * @param {MouseEvent} event 328 */ 329 _onMouseMove: function(event) 330 { 331 var x = event.x - event.target.offsetParent.offsetLeft 332 this._markerXPosition = x; 333 this._refreshCurrentValues(); 334 }, 335 336 _refreshCurrentValues: function() 337 { 338 if (!this._counters.length) 339 return; 340 if (this._markerXPosition === undefined) 341 return; 342 if (this._maximumIndex === -1) 343 return; 344 var i = this._recordIndexAt(this._markerXPosition); 345 346 this._updateCurrentValue(this._counters[i]); 347 348 this._highlightCurrentPositionOnGraphs(this._markerXPosition, i); 349 }, 350 351 _updateCurrentValue: function(counterEntry) 352 { 353 for (var j = 0; j < this._counterUI.length; j++) 354 this._counterUI[j].updateCurrentValue(counterEntry); 355 }, 356 357 _recordIndexAt: function(x) 358 { 359 var i; 360 for (i = this._minimumIndex + 1; i <= this._maximumIndex; i++) { 361 var statX = this._counters[i].x; 362 if (x < statX) 363 break; 364 } 365 i--; 366 return i; 367 }, 368 369 _highlightCurrentPositionOnGraphs: function(x, index) 370 { 371 var ctx = this._canvas.getContext("2d"); 372 this._restoreImageUnderMarker(ctx); 373 this._drawMarker(ctx, x, index); 374 }, 375 376 _restoreImageUnderMarker: function(ctx) 377 { 378 throw new Error("Not implemented"); 379 }, 380 381 _drawMarker: function(ctx, x, index) 382 { 383 throw new Error("Not implemented"); 384 }, 385 386 visible: function() 387 { 388 return this._memorySidebarView.isShowing(); 389 }, 390 391 show: function() 392 { 393 var anchor = /** @type {Element|null} */ (this._containerAnchor.nextSibling); 394 this._memorySidebarView.show(this._timelinePanel.element, anchor); 395 this._updateSize(); 396 this._refreshDividers(); 397 setTimeout(this._draw.bind(this), 0); 398 }, 399 400 refresh: function() 401 { 402 this._updateSize(); 403 this._refreshDividers(); 404 this._draw(); 405 this._refreshCurrentValues(); 406 }, 407 408 hide: function() 409 { 410 this._memorySidebarView.detach(); 411 }, 412 413 _refreshDividers: function() 414 { 415 this._timelineGrid.updateDividers(this._timelinePanel.calculator); 416 }, 417 418 _setVerticalClip: function(originY, height) 419 { 420 this._originY = originY; 421 this._clippedHeight = height; 422 }, 423 424 _calculateXValues: function() 425 { 426 if (!this._counters.length) 427 return; 428 429 var width = this._canvas.width; 430 var xFactor = width / (this._maxTime - this._minTime); 431 432 this._counters[this._minimumIndex].x = 0; 433 for (var i = this._minimumIndex + 1; i < this._maximumIndex; i++) 434 this._counters[i].x = xFactor * (this._counters[i].time - this._minTime); 435 this._counters[this._maximumIndex].x = width; 436 }, 437 438 _clear: function() { 439 var ctx = this._canvas.getContext("2d"); 440 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 441 this._discardImageUnderMarker(); 442 }, 443 444 _discardImageUnderMarker: function() 445 { 446 throw new Error("Not implemented"); 447 } 448} 449 450