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 * @param {function(string=)} showImageCallback 34 * @extends {WebInspector.HBox} 35 */ 36 WebInspector.PaintProfilerView = function(showImageCallback) 37 { 38 WebInspector.View.call(this); 39 this.element.classList.add("paint-profiler-view"); 40 41 this._showImageCallback = showImageCallback; 42 43 this._canvas = this.element.createChild("canvas", "fill"); 44 this._context = this._canvas.getContext("2d"); 45 this._selectionWindow = new WebInspector.OverviewGrid.Window(this.element, this.element); 46 this._selectionWindow.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); 47 48 this._innerBarWidth = 4 * window.devicePixelRatio; 49 this._minBarHeight = 4 * window.devicePixelRatio; 50 this._barPaddingWidth = 2 * window.devicePixelRatio; 51 this._outerBarWidth = this._innerBarWidth + this._barPaddingWidth; 52 53 this._reset(); 54 } 55 56 WebInspector.PaintProfilerView.Events = { 57 WindowChanged: "WindowChanged" 58 }; 59 60 WebInspector.PaintProfilerView.prototype = { 61 onResize: function() 62 { 63 this._update(); 64 }, 65 66 /** 67 * @param {?WebInspector.PaintProfilerSnapshot} snapshot 68 */ 69 setSnapshot: function(snapshot) 70 { 71 this._reset(); 72 this._snapshot = snapshot; 73 if (!this._snapshot) { 74 this._update(); 75 return; 76 } 77 snapshot.requestImage(null, null, this._showImageCallback); 78 snapshot.profile(onProfileDone.bind(this)); 79 /** 80 * @param {!Array.<!LayerTreeAgent.PaintProfile>=} profiles 81 * @this {WebInspector.PaintProfilerView} 82 */ 83 function onProfileDone(profiles) 84 { 85 this._profiles = profiles; 86 this._update(); 87 } 88 }, 89 90 _update: function() 91 { 92 this._canvas.width = this.element.clientWidth * window.devicePixelRatio; 93 this._canvas.height = this.element.clientHeight * window.devicePixelRatio; 94 this._samplesPerBar = 0; 95 if (!this._profiles || !this._profiles.length) 96 return; 97 98 var maxBars = Math.floor((this._canvas.width - 2 * this._barPaddingWidth) / this._outerBarWidth); 99 var sampleCount = this._profiles[0].length; 100 this._samplesPerBar = Math.ceil(sampleCount / maxBars); 101 var barCount = Math.floor(sampleCount / this._samplesPerBar); 102 103 var maxBarTime = 0; 104 var barTimes = []; 105 for (var i = 0, lastBarIndex = 0, lastBarTime = 0; i < sampleCount;) { 106 for (var row = 0; row < this._profiles.length; row++) 107 lastBarTime += this._profiles[row][i]; 108 ++i; 109 if (i - lastBarIndex == this._samplesPerBar || i == sampleCount) { 110 // Normalize by total number of samples accumulated. 111 lastBarTime /= this._profiles.length * (i - lastBarIndex); 112 barTimes.push(lastBarTime); 113 if (lastBarTime > maxBarTime) 114 maxBarTime = lastBarTime; 115 lastBarTime = 0; 116 lastBarIndex = i; 117 } 118 } 119 const paddingHeight = 4 * window.devicePixelRatio; 120 var scale = (this._canvas.height - paddingHeight - this._minBarHeight) / maxBarTime; 121 this._context.fillStyle = "rgba(110, 180, 110, 0.7)"; 122 for (var i = 0; i < barTimes.length; ++i) 123 this._renderBar(i, barTimes[i] * scale + this._minBarHeight); 124 }, 125 126 /** 127 * @param {number} index 128 * @param {number} height 129 */ 130 _renderBar: function(index, height) 131 { 132 var x = this._barPaddingWidth + index * this._outerBarWidth; 133 var y = this._canvas.height - height; 134 this._context.fillRect(x, y, this._innerBarWidth, height); 135 }, 136 137 _onWindowChanged: function() 138 { 139 if (this._updateImageTimer) 140 return; 141 this._updateImageTimer = setTimeout(this._updateImage.bind(this), 100); 142 this.dispatchEventToListeners(WebInspector.PaintProfilerView.Events.WindowChanged); 143 }, 144 145 /** 146 * @return {{left: number, right: number}} 147 */ 148 windowBoundaries: function() 149 { 150 var screenLeft = this._selectionWindow.windowLeft * this._canvas.width; 151 var screenRight = this._selectionWindow.windowRight * this._canvas.width; 152 var barLeft = Math.floor((screenLeft - this._barPaddingWidth) / this._outerBarWidth); 153 var barRight = Math.floor((screenRight - this._barPaddingWidth + this._innerBarWidth)/ this._outerBarWidth); 154 var stepLeft = Math.max(0, barLeft * this._samplesPerBar); 155 var stepRight = Math.min(barRight * this._samplesPerBar, this._profiles[0].length); 156 157 return {left: stepLeft, right: stepRight}; 158 }, 159 160 _updateImage: function() 161 { 162 delete this._updateImageTimer; 163 if (!this._profiles || !this._profiles.length) 164 return; 165 166 var window = this.windowBoundaries(); 167 this._snapshot.requestImage(window.left, window.right, this._showImageCallback); 168 }, 169 170 _reset: function() 171 { 172 this._snapshot = null; 173 this._profiles = null; 174 this._selectionWindow.reset(); 175 }, 176 177 __proto__: WebInspector.HBox.prototype 178 }; 179 180 /** 181 * @constructor 182 * @extends {WebInspector.VBox} 183 */ 184 WebInspector.PaintProfilerCommandLogView = function() 185 { 186 WebInspector.VBox.call(this); 187 this.setMinimumSize(100, 25); 188 this.element.classList.add("outline-disclosure"); 189 var sidebarTreeElement = this.element.createChild("ol", "sidebar-tree"); 190 this.sidebarTree = new TreeOutline(sidebarTreeElement); 191 this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true); 192 this._reset(); 193 } 194 195 WebInspector.PaintProfilerCommandLogView.prototype = { 196 /** 197 * @param {?WebInspector.PaintProfilerSnapshot} snapshot 198 */ 199 setSnapshot: function(snapshot) 200 { 201 this._reset(); 202 if (!snapshot) { 203 this.updateWindow(); 204 return; 205 } 206 snapshot.commandLog(onCommandLogDone.bind(this)); 207 208 /** 209 * @param {!Array.<!Object>=} log 210 * @this {WebInspector.PaintProfilerCommandLogView} 211 */ 212 function onCommandLogDone(log) 213 { 214 this._log = log; 215 this.updateWindow(); 216 } 217 }, 218 219 /** 220 * @param {number=} stepLeft 221 * @param {number=} stepRight 222 */ 223 updateWindow: function(stepLeft, stepRight) 224 { 225 var log = this._log; 226 stepLeft = stepLeft || 0; 227 stepRight = stepRight || log.length - 1; 228 this.sidebarTree.removeChildren(); 229 for (var i = stepLeft; i <= stepRight; ++i) { 230 var node = new WebInspector.LogTreeElement(log[i]); 231 this.sidebarTree.appendChild(node); 232 } 233 }, 234 235 _reset: function() 236 { 237 this._log = []; 238 }, 239 240 /** 241 * @param {!Element} target 242 * @return {!Element} 243 */ 244 _getHoverAnchor: function(target) 245 { 246 return /** @type {!Element} */ (target.enclosingNodeOrSelfWithNodeName("span")); 247 }, 248 249 /** 250 * @param {!Element} element 251 * @param {function(!WebInspector.RemoteObject, boolean, !Element=):undefined} showCallback 252 */ 253 _resolveObjectForPopover: function(element, showCallback) 254 { 255 var liElement = element.enclosingNodeOrSelfWithNodeName("li"); 256 var logItem = liElement.treeElement.representedObject; 257 var obj = {"method": logItem.method}; 258 if (logItem.params) 259 obj.params = logItem.params; 260 showCallback(WebInspector.RemoteObject.fromLocalObject(obj), false); 261 }, 262 263 __proto__: WebInspector.VBox.prototype 264 }; 265 266 /** 267 * @constructor 268 * @param {!Object} logItem 269 * @extends {TreeElement} 270 */ 271 WebInspector.LogTreeElement = function(logItem) 272 { 273 TreeElement.call(this, "", logItem); 274 this._update(); 275 } 276 277 WebInspector.LogTreeElement.prototype = { 278 /** 279 * @param {!Object} param 280 * @param {string} name 281 * @return {string} 282 */ 283 _paramToString: function(param, name) 284 { 285 if (typeof param !== "object") 286 return typeof param === "string" && param.length > 100 ? name : JSON.stringify(param); 287 var str = ""; 288 var keyCount = 0; 289 for (var key in param) { 290 if (++keyCount > 4 || typeof param[key] === "object" || (typeof param[key] === "string" && param[key].length > 100)) 291 return name; 292 if (str) 293 str += ", "; 294 str += param[key]; 295 } 296 return str; 297 }, 298 299 /** 300 * @param {!Object} params 301 * @return {string} 302 */ 303 _paramsToString: function(params) 304 { 305 var str = ""; 306 for (var key in params) { 307 if (str) 308 str += ", "; 309 str += this._paramToString(params[key], key); 310 } 311 return str; 312 }, 313 314 _update: function() 315 { 316 var logItem = this.representedObject; 317 var title = document.createDocumentFragment(); 318 title.createChild("div", "selection"); 319 var span = title.createChild("span"); 320 var textContent = logItem.method; 321 if (logItem.params) 322 textContent += "(" + this._paramsToString(logItem.params) + ")"; 323 span.textContent = textContent; 324 this.title = title; 325 }, 326 327 /** 328 * @param {boolean} hovered 329 */ 330 setHovered: function(hovered) 331 { 332 this.listItemElement.classList.toggle("hovered", hovered); 333 }, 334 335 __proto__: TreeElement.prototype 336 }; 337