Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / timeline / PaintProfilerView.js
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.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));
44     this.element.createChild("div", "paint-profiler-pie-chart").appendChild(this._pieChart.element);
45
46     this._showImageCallback = showImageCallback;
47
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);
52
53     this._innerBarWidth = 4 * window.devicePixelRatio;
54     this._minBarHeight = window.devicePixelRatio;
55     this._barPaddingWidth = 2 * window.devicePixelRatio;
56     this._outerBarWidth = this._innerBarWidth + this._barPaddingWidth;
57
58     this._reset();
59 }
60
61 WebInspector.PaintProfilerView.Events = {
62     WindowChanged: "WindowChanged"
63 };
64
65 WebInspector.PaintProfilerView.prototype = {
66     onResize: function()
67     {
68         this._update();
69     },
70
71     /**
72      * @param {?WebInspector.PaintProfilerSnapshot} snapshot
73      * @param {!Array.<!WebInspector.PaintProfilerLogItem>} log
74      */
75     setSnapshotAndLog: function(snapshot, log)
76     {
77         this._reset();
78         this._snapshot = snapshot;
79         this._log = log;
80         this._logCategories = this._log.map(WebInspector.PaintProfilerView._categoryForLogItem);
81
82         if (!this._snapshot) {
83             this._update();
84             return;
85         }
86         this._progressBanner.classList.remove("hidden");
87         snapshot.requestImage(null, null, 1, this._showImageCallback);
88         snapshot.profile(onProfileDone.bind(this));
89         /**
90          * @param {!Array.<!LayerTreeAgent.PaintProfile>=} profiles
91          * @this {WebInspector.PaintProfilerView}
92          */
93         function onProfileDone(profiles)
94         {
95             this._progressBanner.classList.add("hidden");
96             this._profiles = profiles;
97             this._update();
98         }
99     },
100
101     _update: function()
102     {
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)
107             return;
108
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);
113
114         var maxBarTime = 0;
115         var barTimes = [];
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;
125             }
126             ++i;
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;
133
134                 barTimes.push(lastBarTime);
135                 barHeightByCategory.push(heightByCategory);
136
137                 if (lastBarTime > maxBarTime)
138                     maxBarTime = lastBarTime;
139                 lastBarTime = 0;
140                 heightByCategory = {};
141                 lastBarIndex = i;
142             }
143         }
144
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]);
151         }
152     },
153
154     /**
155      * @param {number} index
156      * @param {!Object.<string, number>} heightByCategory
157      */
158     _renderBar: function(index, heightByCategory)
159     {
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])
165                 continue;
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]);
170         }
171     },
172
173     _onWindowChanged: function()
174     {
175         this.dispatchEventToListeners(WebInspector.PaintProfilerView.Events.WindowChanged);
176
177         // Update pie chart
178         var window = this.windowBoundaries();
179         var totalTime = 0;
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];
187                 totalTime += time;
188                 timeByCategory[category.color] += time;
189             }
190         }
191         this._pieChart.setTotal(totalTime / this._profiles.length);
192         for (var color in timeByCategory)
193           this._pieChart.addSlice(timeByCategory[color] / this._profiles.length, color);
194
195         if (this._updateImageTimer)
196             return;
197         this._updateImageTimer = setTimeout(this._updateImage.bind(this), 100);
198     },
199
200     /**
201      * @param {number} value
202      * @return {string}
203      */
204     _formatPieChartTime: function(value)
205     {
206         return Number.millisToString(value * 1000, true);
207     },
208
209     /**
210      * @return {{left: number, right: number}}
211      */
212     windowBoundaries: function()
213     {
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._barPaddingWidth) / this._outerBarWidth);
217         var barRight = Math.floor((screenRight - this._barPaddingWidth + this._innerBarWidth)/ 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 - 1);
220
221         return { left: stepLeft, right: stepRight };
222     },
223
224     _updateImage: function()
225     {
226         delete this._updateImageTimer;
227         if (!this._profiles || !this._profiles.length)
228             return;
229
230         var window = this.windowBoundaries();
231         this._snapshot.requestImage(this._log[window.left].commandIndex, this._log[window.right].commandIndex, 1, this._showImageCallback);
232     },
233
234     _reset: function()
235     {
236         this._snapshot = null;
237         this._profiles = null;
238         this._selectionWindow.reset();
239     },
240
241     __proto__: WebInspector.HBox.prototype
242 };
243
244 /**
245  * @constructor
246  * @extends {WebInspector.VBox}
247  */
248 WebInspector.PaintProfilerCommandLogView = function()
249 {
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);
258
259     this._reset();
260 }
261
262 WebInspector.PaintProfilerCommandLogView.prototype = {
263     /**
264      * @param {?WebInspector.Target} target
265      * @param {!Array.<!WebInspector.PaintProfilerLogItem>=} log
266      */
267     setCommandLog: function(target, log)
268     {
269         this._target = target;
270         this._log = log;
271         this.updateWindow();
272     },
273
274     /**
275       * @param {!TreeOutline} treeOutline
276       * @param {!WebInspector.PaintProfilerLogItem} logItem
277       */
278     _appendLogItem: function(treeOutline, logItem)
279     {
280         var treeElement = new WebInspector.LogTreeElement(this, logItem);
281         treeOutline.appendChild(treeElement);
282     },
283
284     /**
285      * @param {number=} stepLeft
286      * @param {number=} stepRight
287      */
288     updateWindow: function(stepLeft, stepRight)
289     {
290         this.sidebarTree.removeChildren();
291         if (!this._log)
292             return;
293         stepLeft = stepLeft || 0;
294         stepRight = stepRight || this._log.length - 1;
295         for (var i = stepLeft; i <= stepRight; ++i)
296             this._appendLogItem(this.sidebarTree, this._log[i]);
297     },
298
299     _reset: function()
300     {
301         this._log = [];
302     },
303
304     /**
305      * @param {?Event} event
306      */
307     _onMouseMove: function(event)
308     {
309         var node = this.sidebarTree.treeElementFromPoint(event.pageX, event.pageY);
310         if (node === this._lastHoveredNode || !(node instanceof WebInspector.LogTreeElement))
311             return;
312         if (this._lastHoveredNode)
313             this._lastHoveredNode.setHovered(false);
314         this._lastHoveredNode = node;
315         if (this._lastHoveredNode)
316             this._lastHoveredNode.setHovered(true);
317     },
318
319     /**
320      * @param {?Event} event
321      */
322     _onContextMenu: function(event)
323     {
324         if (!this._target)
325             return;
326         var node = this.sidebarTree.treeElementFromPoint(event.pageX, event.pageY);
327         if (!node || !node.representedObject || !(node instanceof WebInspector.LogTreeElement))
328             return;
329         var logItem = /** @type {!WebInspector.PaintProfilerLogItem} */ (node.representedObject);
330         if (!logItem.nodeId())
331             return;
332         var contextMenu = new WebInspector.ContextMenu(event);
333         var domNode = new WebInspector.DeferredDOMNode(this._target, logItem.nodeId());
334         contextMenu.appendApplicableItems(domNode);
335         contextMenu.show();
336     },
337
338     __proto__: WebInspector.VBox.prototype
339 };
340
341 /**
342   * @constructor
343   * @param {!WebInspector.PaintProfilerCommandLogView} ownerView
344   * @param {!WebInspector.PaintProfilerLogItem} logItem
345   * @extends {TreeElement}
346   */
347 WebInspector.LogTreeElement = function(ownerView, logItem)
348 {
349     TreeElement.call(this, "", logItem);
350     this._ownerView = ownerView;
351     this._filled = false;
352 }
353
354 WebInspector.LogTreeElement.prototype = {
355     onattach: function()
356     {
357         this._update();
358         this.hasChildren = !!this.representedObject.params;
359     },
360
361     onexpand: function()
362     {
363         if (this._filled)
364             return;
365         this._filled = true;
366         for (var param in this.representedObject.params)
367             WebInspector.LogPropertyTreeElement._appendLogPropertyItem(this, param, this.representedObject.params[param]);
368     },
369
370     /**
371       * @param {!Object} param
372       * @param {string} name
373       * @return {string}
374       */
375     _paramToString: function(param, name)
376     {
377         if (typeof param !== "object")
378             return typeof param === "string" && param.length > 100 ? name : JSON.stringify(param);
379         var str = "";
380         var keyCount = 0;
381         for (var key in param) {
382             if (++keyCount > 4 || typeof param[key] === "object" || (typeof param[key] === "string" && param[key].length > 100))
383                 return name;
384             if (str)
385                 str += ", ";
386             str += param[key];
387         }
388         return str;
389     },
390
391     /**
392       * @param {!Object} params
393       * @return {string}
394       */
395     _paramsToString: function(params)
396     {
397         var str = "";
398         for (var key in params) {
399             if (str)
400                 str += ", ";
401             str += this._paramToString(params[key], key);
402         }
403         return str;
404     },
405
406     _update: function()
407     {
408         var logItem = this.representedObject;
409         var title = document.createDocumentFragment();
410         title.createChild("div", "selection");
411         title.createTextChild(logItem.method + "(" + this._paramsToString(logItem.params) + ")");
412         this.title = title;
413     },
414
415     /**
416      * @param {boolean} hovered
417      */
418     setHovered: function(hovered)
419     {
420         this.listItemElement.classList.toggle("hovered", hovered);
421         var target = this._ownerView._target;
422         if (!target)
423             return;
424         if (!hovered) {
425             target.domModel.hideDOMNodeHighlight();
426             return;
427         }
428         var logItem = /** @type {!WebInspector.PaintProfilerLogItem} */ (this.representedObject);
429         if (!logItem)
430             return;
431         var backendNodeId = logItem.nodeId();
432         if (!backendNodeId)
433             return;
434         new WebInspector.DeferredDOMNode(target, backendNodeId).resolve(highlightNode);
435         /**
436          * @param {?WebInspector.DOMNode} node
437          */
438         function highlightNode(node)
439         {
440             if (node)
441                 node.highlight();
442         }
443     },
444
445     __proto__: TreeElement.prototype
446 };
447
448 /**
449   * @constructor
450   * @param {!{name: string, value}} property
451   * @extends {TreeElement}
452   */
453 WebInspector.LogPropertyTreeElement = function(property)
454 {
455     TreeElement.call(this, "", property);
456 };
457
458 /**
459   * @param {!TreeElement} element
460   * @param {string} name
461   * @param {*} value
462   */
463 WebInspector.LogPropertyTreeElement._appendLogPropertyItem = function(element, name, value)
464 {
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]);
470     }
471 };
472
473 WebInspector.LogPropertyTreeElement.prototype = {
474     onattach: function()
475     {
476         var property = this.representedObject;
477         var title = document.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);
487         }
488         this.title = title;
489     },
490
491     __proto__: TreeElement.prototype
492 }
493
494 /**
495  * @return {!Object.<string, !WebInspector.PaintProfilerCategory>}
496  */
497 WebInspector.PaintProfilerView.categories = function()
498 {
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)")
506     };
507     return WebInspector.PaintProfilerView._categories;
508 };
509
510 /**
511  * @constructor
512  * @param {string} name
513  * @param {string} title
514  * @param {string} color
515  */
516 WebInspector.PaintProfilerCategory = function(name, title, color)
517 {
518     this.name = name;
519     this.title = title;
520     this.color = color;
521 }
522
523 /**
524  * @return {!Object.<string, !WebInspector.PaintProfilerCategory>}
525  */
526 WebInspector.PaintProfilerView._initLogItemCategories = function()
527 {
528     if (WebInspector.PaintProfilerView._logItemCategoriesMap)
529         return WebInspector.PaintProfilerView._logItemCategoriesMap;
530
531     var categories = WebInspector.PaintProfilerView.categories();
532
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"];
570
571     WebInspector.PaintProfilerView._logItemCategoriesMap = logItemCategories;
572     return logItemCategories;
573 }
574
575 /**
576  * @param {!Object} logItem
577  * @return {!WebInspector.PaintProfilerCategory}
578  */
579 WebInspector.PaintProfilerView._categoryForLogItem = function(logItem)
580 {
581     var method = logItem.method.toTitleCase();
582
583     var logItemCategories = WebInspector.PaintProfilerView._initLogItemCategories();
584     var result = logItemCategories[method];
585     if (!result) {
586         result = WebInspector.PaintProfilerView.categories()["misc"];
587         logItemCategories[method] = result;
588     }
589     return result;
590 }