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.MemoryStatistics} 34 * @param {WebInspector.TimelinePanel} timelinePanel 35 * @param {WebInspector.TimelineModel} model 36 * @param {number} sidebarWidth 37 */ 38WebInspector.DOMCountersGraph = function(timelinePanel, model, sidebarWidth) 39{ 40 WebInspector.MemoryStatistics.call(this, timelinePanel, model, sidebarWidth); 41} 42 43/** 44 * @constructor 45 * @extends {WebInspector.CounterUIBase} 46 * @param {WebInspector.DOMCountersGraph} memoryCountersPane 47 * @param {string} title 48 * @param {string} currentValueLabel 49 * @param {Array.<number>} rgb 50 * @param {function(WebInspector.DOMCountersGraph.Counter):number} valueGetter 51 */ 52WebInspector.DOMCounterUI = function(memoryCountersPane, title, currentValueLabel, rgb, valueGetter) 53{ 54 var swatchColor = "rgb(" + rgb.join(",") + ")"; 55 WebInspector.CounterUIBase.call(this, memoryCountersPane, title, swatchColor, valueGetter) 56 this._range = this._swatch.element.createChild("span"); 57 58 this._value = memoryCountersPane._currentValuesBar.createChild("span", "memory-counter-value"); 59 this._value.style.color = swatchColor; 60 this._currentValueLabel = currentValueLabel; 61 62 this.graphColor = "rgba(" + rgb.join(",") + ",0.8)"; 63 this.graphYValues = []; 64} 65 66/** 67 * @constructor 68 * @extends {WebInspector.MemoryStatistics.Counter} 69 * @param {number} time 70 * @param {number} documentCount 71 * @param {number} nodeCount 72 * @param {number} listenerCount 73 */ 74WebInspector.DOMCountersGraph.Counter = function(time, documentCount, nodeCount, listenerCount) 75{ 76 WebInspector.MemoryStatistics.Counter.call(this, time); 77 this.documentCount = documentCount; 78 this.nodeCount = nodeCount; 79 this.listenerCount = listenerCount; 80} 81 82WebInspector.DOMCounterUI.prototype = { 83 /** 84 * @param {number} minValue 85 * @param {number} maxValue 86 */ 87 setRange: function(minValue, maxValue) 88 { 89 this._range.textContent = WebInspector.UIString("[ %d - %d ]", minValue, maxValue); 90 }, 91 92 updateCurrentValue: function(countersEntry) 93 { 94 this._value.textContent = WebInspector.UIString(this._currentValueLabel, this.valueGetter(countersEntry)); 95 }, 96 97 clearCurrentValueAndMarker: function(ctx) 98 { 99 this._value.textContent = ""; 100 this.restoreImageUnderMarker(ctx); 101 }, 102 103 /** 104 * @param {CanvasRenderingContext2D} ctx 105 * @param {number} x 106 * @param {number} y 107 * @param {number} radius 108 */ 109 saveImageUnderMarker: function(ctx, x, y, radius) 110 { 111 const w = radius + 1; 112 var imageData = ctx.getImageData(x - w, y - w, 2 * w, 2 * w); 113 this._imageUnderMarker = { 114 x: x - w, 115 y: y - w, 116 imageData: imageData 117 }; 118 }, 119 120 /** 121 * @param {CanvasRenderingContext2D} ctx 122 */ 123 restoreImageUnderMarker: function(ctx) 124 { 125 if (!this.visible) 126 return; 127 if (this._imageUnderMarker) 128 ctx.putImageData(this._imageUnderMarker.imageData, this._imageUnderMarker.x, this._imageUnderMarker.y); 129 this.discardImageUnderMarker(); 130 }, 131 132 discardImageUnderMarker: function() 133 { 134 delete this._imageUnderMarker; 135 }, 136 137 __proto__: WebInspector.CounterUIBase.prototype 138} 139 140 141WebInspector.DOMCountersGraph.prototype = { 142 _createCurrentValuesBar: function() 143 { 144 this._currentValuesBar = this._canvasContainer.createChild("div"); 145 this._currentValuesBar.id = "counter-values-bar"; 146 this._canvasContainer.addStyleClass("dom-counters"); 147 }, 148 149 /** 150 * @return {Array.<WebInspector.DOMCounterUI>} 151 */ 152 _createCounterUIList: function() 153 { 154 function getDocumentCount(entry) 155 { 156 return entry.documentCount; 157 } 158 function getNodeCount(entry) 159 { 160 return entry.nodeCount; 161 } 162 function getListenerCount(entry) 163 { 164 return entry.listenerCount; 165 } 166 return [ 167 new WebInspector.DOMCounterUI(this, "Document Count", "Documents: %d", [100, 0, 0], getDocumentCount), 168 new WebInspector.DOMCounterUI(this, "DOM Node Count", "Nodes: %d", [0, 100, 0], getNodeCount), 169 new WebInspector.DOMCounterUI(this, "Event Listener Count", "Listeners: %d", [0, 0, 100], getListenerCount) 170 ]; 171 }, 172 173 _canvasHeight: function() 174 { 175 return this._canvasContainer.offsetHeight - this._currentValuesBar.offsetHeight; 176 }, 177 178 /** 179 * @param {WebInspector.Event} event 180 */ 181 _onRecordAdded: function(event) 182 { 183 function addStatistics(record) 184 { 185 var counters = record["counters"]; 186 if (!counters) 187 return; 188 this._counters.push(new WebInspector.DOMCountersGraph.Counter( 189 record.endTime || record.startTime, 190 counters["documents"], 191 counters["nodes"], 192 counters["jsEventListeners"] 193 )); 194 } 195 WebInspector.TimelinePresentationModel.forAllRecords([event.data], null, addStatistics.bind(this)); 196 }, 197 198 _draw: function() 199 { 200 WebInspector.MemoryStatistics.prototype._draw.call(this); 201 for (var i = 0; i < this._counterUI.length; i++) 202 this._drawGraph(this._counterUI[i]); 203 }, 204 205 /** 206 * @param {CanvasRenderingContext2D} ctx 207 */ 208 _restoreImageUnderMarker: function(ctx) 209 { 210 for (var i = 0; i < this._counterUI.length; i++) { 211 var counterUI = this._counterUI[i]; 212 if (!counterUI.visible) 213 continue; 214 counterUI.restoreImageUnderMarker(ctx); 215 } 216 }, 217 218 /** 219 * @param {CanvasRenderingContext2D} ctx 220 * @param {number} x 221 * @param {number} index 222 */ 223 _saveImageUnderMarker: function(ctx, x, index) 224 { 225 const radius = 2; 226 for (var i = 0; i < this._counterUI.length; i++) { 227 var counterUI = this._counterUI[i]; 228 if (!counterUI.visible) 229 continue; 230 var y = counterUI.graphYValues[index]; 231 counterUI.saveImageUnderMarker(ctx, x, y, radius); 232 } 233 }, 234 235 /** 236 * @param {CanvasRenderingContext2D} ctx 237 * @param {number} x 238 * @param {number} index 239 */ 240 _drawMarker: function(ctx, x, index) 241 { 242 this._saveImageUnderMarker(ctx, x, index); 243 const radius = 2; 244 for (var i = 0; i < this._counterUI.length; i++) { 245 var counterUI = this._counterUI[i]; 246 if (!counterUI.visible) 247 continue; 248 var y = counterUI.graphYValues[index]; 249 ctx.beginPath(); 250 ctx.arc(x, y, radius, 0, Math.PI * 2, true); 251 ctx.lineWidth = 1; 252 ctx.fillStyle = counterUI.graphColor; 253 ctx.strokeStyle = counterUI.graphColor; 254 ctx.fill(); 255 ctx.stroke(); 256 ctx.closePath(); 257 } 258 }, 259 260 /** 261 * @param {WebInspector.CounterUIBase} counterUI 262 */ 263 _drawGraph: function(counterUI) 264 { 265 var canvas = this._canvas; 266 var ctx = canvas.getContext("2d"); 267 var width = canvas.width; 268 var height = this._clippedHeight; 269 var originY = this._originY; 270 var valueGetter = counterUI.valueGetter; 271 272 if (!this._counters.length) 273 return; 274 275 var maxValue; 276 var minValue; 277 for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { 278 var value = valueGetter(this._counters[i]); 279 if (minValue === undefined || value < minValue) 280 minValue = value; 281 if (maxValue === undefined || value > maxValue) 282 maxValue = value; 283 } 284 285 counterUI.setRange(minValue, maxValue); 286 287 if (!counterUI.visible) 288 return; 289 290 var yValues = counterUI.graphYValues; 291 yValues.length = this._counters.length; 292 293 var maxYRange = maxValue - minValue; 294 var yFactor = maxYRange ? height / (maxYRange) : 1; 295 296 ctx.beginPath(); 297 var currentY = originY + (height - (valueGetter(this._counters[this._minimumIndex]) - minValue) * yFactor); 298 ctx.moveTo(0, currentY); 299 for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { 300 var x = this._counters[i].x; 301 ctx.lineTo(x, currentY); 302 currentY = originY + (height - (valueGetter(this._counters[i]) - minValue) * yFactor); 303 ctx.lineTo(x, currentY); 304 305 yValues[i] = currentY; 306 } 307 ctx.lineTo(width, currentY); 308 ctx.lineWidth = 1; 309 ctx.strokeStyle = counterUI.graphColor; 310 ctx.stroke(); 311 ctx.closePath(); 312 }, 313 314 _discardImageUnderMarker: function() 315 { 316 for (var i = 0; i < this._counterUI.length; i++) 317 this._counterUI[i].discardImageUnderMarker(); 318 }, 319 320 __proto__: WebInspector.MemoryStatistics.prototype 321} 322 323