2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
33 * @param {function(string=)} showImageCallback
34 * @extends {WebInspector.HBox}
36 WebInspector.PaintProfilerView = function(showImageCallback)
38 WebInspector.HBox.call(this);
39 this.element.classList.add("paint-profiler-overview", "hbox");
40 this._canvasContainer = this.element.createChild("div", "paint-profiler-canvas-container");
41 this._progressBanner = this.element.createChild("div", "fill progress-banner hidden");
42 this._progressBanner.textContent = WebInspector.UIString("Profiling\u2026");
43 this._pieChart = new WebInspector.PieChart(55, this._formatPieChartTime.bind(this), true);
44 this.element.createChild("div", "paint-profiler-pie-chart").appendChild(this._pieChart.element);
46 this._showImageCallback = showImageCallback;
48 this._canvas = this._canvasContainer.createChild("canvas", "fill");
49 this._context = this._canvas.getContext("2d");
50 this._selectionWindow = new WebInspector.OverviewGrid.Window(this._canvasContainer);
51 this._selectionWindow.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
53 this._innerBarWidth = 4 * window.devicePixelRatio;
54 this._minBarHeight = window.devicePixelRatio;
55 this._barPaddingWidth = 2 * window.devicePixelRatio;
56 this._outerBarWidth = this._innerBarWidth + this._barPaddingWidth;
61 WebInspector.PaintProfilerView.Events = {
62 WindowChanged: "WindowChanged"
65 WebInspector.PaintProfilerView.prototype = {
72 * @param {?WebInspector.PaintProfilerSnapshot} snapshot
73 * @param {!Array.<!WebInspector.PaintProfilerLogItem>} log
75 setSnapshotAndLog: function(snapshot, log)
78 this._snapshot = snapshot;
80 this._logCategories = this._log.map(WebInspector.PaintProfilerView._categoryForLogItem);
82 if (!this._snapshot) {
86 this._progressBanner.classList.remove("hidden");
87 snapshot.requestImage(null, null, 1, this._showImageCallback);
88 snapshot.profile(onProfileDone.bind(this));
90 * @param {!Array.<!LayerTreeAgent.PaintProfile>=} profiles
91 * @this {WebInspector.PaintProfilerView}
93 function onProfileDone(profiles)
95 this._progressBanner.classList.add("hidden");
96 this._profiles = profiles;
103 this._canvas.width = this._canvasContainer.clientWidth * window.devicePixelRatio;
104 this._canvas.height = this._canvasContainer.clientHeight * window.devicePixelRatio;
105 this._samplesPerBar = 0;
106 if (!this._profiles || !this._profiles.length)
109 var maxBars = Math.floor((this._canvas.width - 2 * this._barPaddingWidth) / this._outerBarWidth);
110 var sampleCount = this._log.length;
111 this._samplesPerBar = Math.ceil(sampleCount / maxBars);
112 var barCount = Math.floor(sampleCount / this._samplesPerBar);
116 var barHeightByCategory = [];
117 var heightByCategory = {};
118 for (var i = 0, lastBarIndex = 0, lastBarTime = 0; i < sampleCount;) {
119 var categoryName = (this._logCategories[i] && this._logCategories[i].name) || "misc";
120 var sampleIndex = this._log[i].commandIndex;
121 for (var row = 0; row < this._profiles.length; row++) {
122 var sample = this._profiles[row][sampleIndex];
123 lastBarTime += sample;
124 heightByCategory[categoryName] = (heightByCategory[categoryName] || 0) + sample;
127 if (i - lastBarIndex == this._samplesPerBar || i == sampleCount) {
128 // Normalize by total number of samples accumulated.
129 var factor = this._profiles.length * (i - lastBarIndex);
130 lastBarTime /= factor;
131 for (categoryName in heightByCategory)
132 heightByCategory[categoryName] /= factor;
134 barTimes.push(lastBarTime);
135 barHeightByCategory.push(heightByCategory);
137 if (lastBarTime > maxBarTime)
138 maxBarTime = lastBarTime;
140 heightByCategory = {};
145 const paddingHeight = 4 * window.devicePixelRatio;
146 var scale = (this._canvas.height - paddingHeight - this._minBarHeight) / maxBarTime;
147 for (var i = 0; i < barTimes.length; ++i) {
148 for (var categoryName in barHeightByCategory[i])
149 barHeightByCategory[i][categoryName] *= (barTimes[i] * scale + this._minBarHeight) / barTimes[i];
150 this._renderBar(i, barHeightByCategory[i]);
155 * @param {number} index
156 * @param {!Object.<string, number>} heightByCategory
158 _renderBar: function(index, heightByCategory)
160 var categories = WebInspector.PaintProfilerView.categories();
161 var currentHeight = 0;
162 var x = this._barPaddingWidth + index * this._outerBarWidth;
163 for (var categoryName in categories) {
164 if (!heightByCategory[categoryName])
166 currentHeight += heightByCategory[categoryName];
167 var y = this._canvas.height - currentHeight;
168 this._context.fillStyle = categories[categoryName].color;
169 this._context.fillRect(x, y, this._innerBarWidth, heightByCategory[categoryName]);
173 _onWindowChanged: function()
175 this.dispatchEventToListeners(WebInspector.PaintProfilerView.Events.WindowChanged);
178 var window = this.windowBoundaries();
180 var timeByCategory = {};
181 for (var i = window.left; i < window.right; ++i) {
182 var logEntry = this._log[i];
183 var category = WebInspector.PaintProfilerView._categoryForLogItem(logEntry);
184 timeByCategory[category.color] = timeByCategory[category.color] || 0;
185 for (var j = 0; j < this._profiles.length; ++j) {
186 var time = this._profiles[j][logEntry.commandIndex];
188 timeByCategory[category.color] += time;
191 this._pieChart.setTotal(totalTime / this._profiles.length);
192 for (var color in timeByCategory)
193 this._pieChart.addSlice(timeByCategory[color] / this._profiles.length, color);
195 if (this._updateImageTimer)
197 this._updateImageTimer = setTimeout(this._updateImage.bind(this), 100);
201 * @param {number} value
204 _formatPieChartTime: function(value)
206 return Number.millisToString(value * 1000, true);
210 * @return {{left: number, right: number}}
212 windowBoundaries: function()
214 var screenLeft = this._selectionWindow.windowLeft * this._canvas.width;
215 var screenRight = this._selectionWindow.windowRight * this._canvas.width;
216 var barLeft = Math.floor(screenLeft / this._outerBarWidth);
217 var barRight = Math.floor((screenRight + this._innerBarWidth - this._barPaddingWidth / 2) / this._outerBarWidth);
218 var stepLeft = Number.constrain(barLeft * this._samplesPerBar, 0, this._log.length - 1);
219 var stepRight = Number.constrain(barRight * this._samplesPerBar, 0, this._log.length);
221 return { left: stepLeft, right: stepRight };
224 _updateImage: function()
226 delete this._updateImageTimer;
227 if (!this._profiles || !this._profiles.length)
230 var window = this.windowBoundaries();
231 this._snapshot.requestImage(this._log[window.left].commandIndex, this._log[window.right - 1].commandIndex, 1, this._showImageCallback);
236 this._snapshot = null;
237 this._profiles = null;
238 this._selectionWindow.reset();
241 __proto__: WebInspector.HBox.prototype
246 * @extends {WebInspector.VBox}
248 WebInspector.PaintProfilerCommandLogView = function()
250 WebInspector.VBox.call(this);
251 this.setMinimumSize(100, 25);
252 this.element.classList.add("outline-disclosure", "profiler-log-view", "section");
253 var sidebarTreeElement = this.element.createChild("ol", "sidebar-tree properties monospace");
254 sidebarTreeElement.addEventListener("mousemove", this._onMouseMove.bind(this), false);
255 sidebarTreeElement.addEventListener("mouseout", this._onMouseMove.bind(this), false);
256 sidebarTreeElement.addEventListener("contextmenu", this._onContextMenu.bind(this), true);
257 this.sidebarTree = new TreeOutline(sidebarTreeElement);
262 WebInspector.PaintProfilerCommandLogView.prototype = {
264 * @param {?WebInspector.Target} target
265 * @param {!Array.<!WebInspector.PaintProfilerLogItem>=} log
267 setCommandLog: function(target, log)
269 this._target = target;
275 * @param {!TreeOutline} treeOutline
276 * @param {!WebInspector.PaintProfilerLogItem} logItem
278 _appendLogItem: function(treeOutline, logItem)
280 var treeElement = new WebInspector.LogTreeElement(this, logItem);
281 treeOutline.appendChild(treeElement);
285 * @param {number=} stepLeft
286 * @param {number=} stepRight
288 updateWindow: function(stepLeft, stepRight)
290 this.sidebarTree.removeChildren();
293 stepLeft = stepLeft || 0;
294 stepRight = stepRight || this._log.length;
295 for (var i = stepLeft; i < stepRight; ++i)
296 this._appendLogItem(this.sidebarTree, this._log[i]);
305 * @param {?Event} event
307 _onMouseMove: function(event)
309 var node = this.sidebarTree.treeElementFromEvent(event);
310 if (node === this._lastHoveredNode || !(node instanceof WebInspector.LogTreeElement))
312 if (this._lastHoveredNode)
313 this._lastHoveredNode.setHovered(false);
314 this._lastHoveredNode = node;
315 if (this._lastHoveredNode)
316 this._lastHoveredNode.setHovered(true);
320 * @param {!Event} event
322 _onContextMenu: function(event)
326 var node = this.sidebarTree.treeElementFromEvent(event);
327 if (!node || !node.representedObject || !(node instanceof WebInspector.LogTreeElement))
329 var logItem = /** @type {!WebInspector.PaintProfilerLogItem} */ (node.representedObject);
330 if (!logItem.nodeId())
332 var contextMenu = new WebInspector.ContextMenu(event);
333 var domNode = new WebInspector.DeferredDOMNode(this._target, logItem.nodeId());
334 contextMenu.appendApplicableItems(domNode);
338 __proto__: WebInspector.VBox.prototype
343 * @param {!WebInspector.PaintProfilerCommandLogView} ownerView
344 * @param {!WebInspector.PaintProfilerLogItem} logItem
345 * @extends {TreeElement}
347 WebInspector.LogTreeElement = function(ownerView, logItem)
349 TreeElement.call(this, "", logItem);
350 this._ownerView = ownerView;
351 this._filled = false;
354 WebInspector.LogTreeElement.prototype = {
358 this.hasChildren = !!this.representedObject.params;
366 for (var param in this.representedObject.params)
367 WebInspector.LogPropertyTreeElement._appendLogPropertyItem(this, param, this.representedObject.params[param]);
371 * @param {!Object} param
372 * @param {string} name
375 _paramToString: function(param, name)
377 if (typeof param !== "object")
378 return typeof param === "string" && param.length > 100 ? name : JSON.stringify(param);
381 for (var key in param) {
382 if (++keyCount > 4 || typeof param[key] === "object" || (typeof param[key] === "string" && param[key].length > 100))
392 * @param {!Object} params
395 _paramsToString: function(params)
398 for (var key in params) {
401 str += this._paramToString(params[key], key);
408 var logItem = this.representedObject;
409 var title = createDocumentFragment();
410 title.createChild("div", "selection");
411 title.createTextChild(logItem.method + "(" + this._paramsToString(logItem.params) + ")");
416 * @param {boolean} hovered
418 setHovered: function(hovered)
420 this.listItemElement.classList.toggle("hovered", hovered);
421 var target = this._ownerView._target;
425 target.domModel.hideDOMNodeHighlight();
428 var logItem = /** @type {!WebInspector.PaintProfilerLogItem} */ (this.representedObject);
431 var backendNodeId = logItem.nodeId();
434 new WebInspector.DeferredDOMNode(target, backendNodeId).resolve(highlightNode);
436 * @param {?WebInspector.DOMNode} node
438 function highlightNode(node)
445 __proto__: TreeElement.prototype
450 * @param {!{name: string, value}} property
451 * @extends {TreeElement}
453 WebInspector.LogPropertyTreeElement = function(property)
455 TreeElement.call(this, "", property);
459 * @param {!TreeElement} element
460 * @param {string} name
463 WebInspector.LogPropertyTreeElement._appendLogPropertyItem = function(element, name, value)
465 var treeElement = new WebInspector.LogPropertyTreeElement({name: name, value: value});
466 element.appendChild(treeElement);
467 if (value && typeof value === "object") {
468 for (var property in value)
469 WebInspector.LogPropertyTreeElement._appendLogPropertyItem(treeElement, property, value[property]);
473 WebInspector.LogPropertyTreeElement.prototype = {
476 var property = this.representedObject;
477 var title = createDocumentFragment();
478 title.createChild("div", "selection");
479 var nameElement = title.createChild("span", "name");
480 nameElement.textContent = property.name;
481 var separatorElement = title.createChild("span", "separator");
482 separatorElement.textContent = ": ";
483 if (property.value === null || typeof property.value !== "object") {
484 var valueElement = title.createChild("span", "value");
485 valueElement.textContent = JSON.stringify(property.value);
486 valueElement.classList.add("console-formatted-" + property.value === null ? "null" : typeof property.value);
491 __proto__: TreeElement.prototype
495 * @return {!Object.<string, !WebInspector.PaintProfilerCategory>}
497 WebInspector.PaintProfilerView.categories = function()
499 if (WebInspector.PaintProfilerView._categories)
500 return WebInspector.PaintProfilerView._categories;
501 WebInspector.PaintProfilerView._categories = {
502 shapes: new WebInspector.PaintProfilerCategory("shapes", WebInspector.UIString("Shapes"), "rgb(255, 161, 129)"),
503 bitmap: new WebInspector.PaintProfilerCategory("bitmap", WebInspector.UIString("Bitmap"), "rgb(136, 196, 255)"),
504 text: new WebInspector.PaintProfilerCategory("text", WebInspector.UIString("Text"), "rgb(180, 255, 137)"),
505 misc: new WebInspector.PaintProfilerCategory("misc", WebInspector.UIString("Misc"), "rgb(206, 160, 255)")
507 return WebInspector.PaintProfilerView._categories;
512 * @param {string} name
513 * @param {string} title
514 * @param {string} color
516 WebInspector.PaintProfilerCategory = function(name, title, color)
524 * @return {!Object.<string, !WebInspector.PaintProfilerCategory>}
526 WebInspector.PaintProfilerView._initLogItemCategories = function()
528 if (WebInspector.PaintProfilerView._logItemCategoriesMap)
529 return WebInspector.PaintProfilerView._logItemCategoriesMap;
531 var categories = WebInspector.PaintProfilerView.categories();
533 var logItemCategories = {};
534 logItemCategories["Clear"] = categories["misc"];
535 logItemCategories["DrawPaint"] = categories["misc"];
536 logItemCategories["DrawData"] = categories["misc"];
537 logItemCategories["SetMatrix"] = categories["misc"];
538 logItemCategories["PushCull"] = categories["misc"];
539 logItemCategories["PopCull"] = categories["misc"];
540 logItemCategories["Translate"] = categories["misc"];
541 logItemCategories["Scale"] = categories["misc"];
542 logItemCategories["Concat"] = categories["misc"];
543 logItemCategories["Restore"] = categories["misc"];
544 logItemCategories["SaveLayer"] = categories["misc"];
545 logItemCategories["Save"] = categories["misc"];
546 logItemCategories["BeginCommentGroup"] = categories["misc"];
547 logItemCategories["AddComment"] = categories["misc"];
548 logItemCategories["EndCommentGroup"] = categories["misc"];
549 logItemCategories["ClipRect"] = categories["misc"];
550 logItemCategories["ClipRRect"] = categories["misc"];
551 logItemCategories["ClipPath"] = categories["misc"];
552 logItemCategories["ClipRegion"] = categories["misc"];
553 logItemCategories["DrawPoints"] = categories["shapes"];
554 logItemCategories["DrawRect"] = categories["shapes"];
555 logItemCategories["DrawOval"] = categories["shapes"];
556 logItemCategories["DrawRRect"] = categories["shapes"];
557 logItemCategories["DrawPath"] = categories["shapes"];
558 logItemCategories["DrawVertices"] = categories["shapes"];
559 logItemCategories["DrawDRRect"] = categories["shapes"];
560 logItemCategories["DrawBitmap"] = categories["bitmap"];
561 logItemCategories["DrawBitmapRectToRect"] = categories["bitmap"];
562 logItemCategories["DrawBitmapMatrix"] = categories["bitmap"];
563 logItemCategories["DrawBitmapNine"] = categories["bitmap"];
564 logItemCategories["DrawSprite"] = categories["bitmap"];
565 logItemCategories["DrawPicture"] = categories["bitmap"];
566 logItemCategories["DrawText"] = categories["text"];
567 logItemCategories["DrawPosText"] = categories["text"];
568 logItemCategories["DrawPosTextH"] = categories["text"];
569 logItemCategories["DrawTextOnPath"] = categories["text"];
571 WebInspector.PaintProfilerView._logItemCategoriesMap = logItemCategories;
572 return logItemCategories;
576 * @param {!Object} logItem
577 * @return {!WebInspector.PaintProfilerCategory}
579 WebInspector.PaintProfilerView._categoryForLogItem = function(logItem)
581 var method = logItem.method.toTitleCase();
583 var logItemCategories = WebInspector.PaintProfilerView._initLogItemCategories();
584 var result = logItemCategories[method];
586 result = WebInspector.PaintProfilerView.categories()["misc"];
587 logItemCategories[method] = result;