Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / TimelineFrameOverview.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  * @extends {WebInspector.TimelineOverviewBase}
34  * @param {!WebInspector.TimelineModel} model
35  * @param {!WebInspector.TimelineFrameModel} frameModel
36  */
37 WebInspector.TimelineFrameOverview = function(model, frameModel)
38 {
39     WebInspector.TimelineOverviewBase.call(this, model);
40     this.element.id = "timeline-overview-frames";
41     this._frameModel = frameModel;
42     this.reset();
43
44     this._outerPadding = 4 * window.devicePixelRatio;
45     this._maxInnerBarWidth = 10 * window.devicePixelRatio;
46     this._topPadding = 6 * window.devicePixelRatio;
47
48     // The below two are really computed by update() -- but let's have something so that windowTimes() is happy.
49     this._actualPadding = 5 * window.devicePixelRatio;
50     this._actualOuterBarWidth = this._maxInnerBarWidth + this._actualPadding;
51
52     this._fillStyles = {};
53     var categories = WebInspector.TimelinePresentationModel.categories();
54     for (var category in categories)
55         this._fillStyles[category] = WebInspector.TimelinePresentationModel.createFillStyleForCategory(this._context, this._maxInnerBarWidth, 0, categories[category]);
56     this._frameTopShadeGradient = this._context.createLinearGradient(0, 0, 0, this._topPadding);
57     this._frameTopShadeGradient.addColorStop(0, "rgba(255, 255, 255, 0.9)");
58     this._frameTopShadeGradient.addColorStop(1, "rgba(255, 255, 255, 0.2)");
59 }
60
61 WebInspector.TimelineFrameOverview.prototype = {
62     /**
63      * @return {!Object.<string, !CanvasGradient>}
64      */
65     categoryFillStyles: function()
66     {
67         return this._fillStyles;
68     },
69
70     reset: function()
71     {
72         this._recordsPerBar = 1;
73         /** @type {!Array.<!{startTime:number, endTime:number}>} */
74         this._barTimes = [];
75     },
76
77     update: function()
78     {
79         this.resetCanvas();
80         this._barTimes = [];
81
82         const minBarWidth = 4 * window.devicePixelRatio;
83         var frames = this._frameModel.frames();
84         var framesPerBar = Math.max(1, frames.length * minBarWidth / this._canvas.width);
85         var visibleFrames = this._aggregateFrames(frames, framesPerBar);
86
87         this._context.save();
88         var scale = (this._canvas.height - this._topPadding) / this._computeTargetFrameLength(visibleFrames);
89         this._renderBars(frames, scale, this._canvas.height);
90         this._context.fillStyle = this._frameTopShadeGradient;
91         this._context.fillRect(0, 0, this._canvas.width, this._topPadding);
92         this._drawFPSMarks(scale, this._canvas.height);
93         this._context.restore();
94     },
95
96     /**
97      * @param {number} x0
98      * @param {number} y0
99      * @param {number} width
100      * @param {number} height
101      */
102     _setCanvasWindow: function(x0, y0, width, height)
103     {
104         this._context.translate(x0, y0);
105         this._context.beginPath();
106         this._context.moveTo(0, 0);
107         this._context.lineTo(width, 0);
108         this._context.lineTo(width, height);
109         this._context.lineTo(0, height);
110         this._context.lineTo(0, 0);
111         this._context.clip();
112     },
113
114     /**
115      * @param {!Array.<!WebInspector.TimelineFrame>} frames
116      * @param {number} framesPerBar
117      * @return {!Array.<!WebInspector.TimelineFrame>}
118      */
119     _aggregateFrames: function(frames, framesPerBar)
120     {
121         var visibleFrames = [];
122         for (var barNumber = 0, currentFrame = 0; currentFrame < frames.length; ++barNumber) {
123             var barStartTime = frames[currentFrame].startTime;
124             var longestFrame = null;
125             var longestDuration = 0;
126
127             for (var lastFrame = Math.min(Math.floor((barNumber + 1) * framesPerBar), frames.length);
128                  currentFrame < lastFrame; ++currentFrame) {
129                 var duration = frames[currentFrame].duration;
130                 if (!longestFrame || longestDuration < duration) {
131                     longestFrame = frames[currentFrame];
132                     longestDuration = duration;
133                 }
134             }
135             var barEndTime = frames[currentFrame - 1].endTime;
136             if (longestFrame) {
137                 visibleFrames.push(longestFrame);
138                 this._barTimes.push({ startTime: barStartTime, endTime: barEndTime });
139             }
140         }
141         return visibleFrames;
142     },
143
144     /**
145      * @param {!Array.<!WebInspector.TimelineFrame>} frames
146      * @return {number}
147      */
148     _computeTargetFrameLength: function(frames)
149     {
150         var durations = [];
151         for (var i = 0; i < frames.length; ++i) {
152             if (frames[i])
153                 durations.push(frames[i].duration);
154         }
155         var medianFrameLength = durations.qselect(Math.floor(durations.length / 2));
156
157         // Optimize appearance for 30fps, but leave some space so it's evident when a frame overflows.
158         // However, if at least half frames won't fit at this scale, fall back to using autoscale.
159         const targetFPS = 20;
160         var result = 1.0 / targetFPS;
161         if (result >= medianFrameLength)
162             return result;
163
164         var maxFrameLength = Math.max.apply(Math, durations);
165         return Math.min(medianFrameLength * 2, maxFrameLength);
166     },
167
168     /**
169      * @param {!Array.<!WebInspector.TimelineFrame>} frames
170      * @param {number} scale
171      * @param {number} windowHeight
172      */
173     _renderBars: function(frames, scale, windowHeight)
174     {
175         const maxPadding = 5 * window.devicePixelRatio;
176         this._actualOuterBarWidth = Math.min((this._canvas.width - 2 * this._outerPadding) / frames.length, this._maxInnerBarWidth + maxPadding);
177         this._actualPadding = Math.min(Math.floor(this._actualOuterBarWidth / 3), maxPadding);
178
179         var barWidth = this._actualOuterBarWidth - this._actualPadding;
180         for (var i = 0; i < frames.length; ++i) {
181             if (frames[i])
182                 this._renderBar(this._barNumberToScreenPosition(i), barWidth, windowHeight, frames[i], scale);
183         }
184     },
185
186     /**
187      * @param {number} n
188      */
189     _barNumberToScreenPosition: function(n)
190     {
191         return this._outerPadding + this._actualOuterBarWidth * n;
192     },
193
194     /**
195      * @param {number} scale
196      * @param {number} height
197      */
198     _drawFPSMarks: function(scale, height)
199     {
200         const fpsMarks = [30, 60];
201
202         this._context.save();
203         this._context.beginPath();
204         this._context.font = (10 * window.devicePixelRatio) + "px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
205         this._context.textAlign = "right";
206         this._context.textBaseline = "alphabetic";
207
208         const labelPadding = 4 * window.devicePixelRatio;
209         const baselineHeight = 3 * window.devicePixelRatio;
210         var lineHeight = 12 * window.devicePixelRatio;
211         var labelTopMargin = 0;
212         var labelOffsetY = 0; // Labels are going to be under their grid lines.
213
214         for (var i = 0; i < fpsMarks.length; ++i) {
215             var fps = fpsMarks[i];
216             // Draw lines one pixel above they need to be, so 60pfs line does not cross most of the frames tops.
217             var y = height - Math.floor(1.0 / fps * scale) - 0.5;
218             var label = WebInspector.UIString("%d\u2009fps", fps);
219             var labelWidth = this._context.measureText(label).width + 2 * labelPadding;
220             var labelX = this._canvas.width;
221
222             if (!i && labelTopMargin < y - lineHeight)
223                 labelOffsetY = -lineHeight; // Labels are going to be over their grid lines.
224             var labelY = y + labelOffsetY;
225             if (labelY < labelTopMargin || labelY + lineHeight > height)
226                 break; // No space for the label, so no line as well.
227
228             this._context.moveTo(0, y);
229             this._context.lineTo(this._canvas.width, y);
230
231             this._context.fillStyle = "rgba(255, 255, 255, 0.5)";
232             this._context.fillRect(labelX - labelWidth, labelY, labelWidth, lineHeight);
233             this._context.fillStyle = "black";
234             this._context.fillText(label, labelX - labelPadding, labelY + lineHeight - baselineHeight);
235             labelTopMargin = labelY + lineHeight;
236         }
237         this._context.strokeStyle = "rgba(60, 60, 60, 0.4)";
238         this._context.stroke();
239         this._context.restore();
240     },
241
242     /**
243      * @param {number} left
244      * @param {number} width
245      * @param {number} windowHeight
246      * @param {!WebInspector.TimelineFrame} frame
247      * @param {number} scale
248      */
249     _renderBar: function(left, width, windowHeight, frame, scale)
250     {
251         var categories = Object.keys(WebInspector.TimelinePresentationModel.categories());
252         var x = Math.floor(left) + 0.5;
253         width = Math.floor(width);
254
255         var totalCPUTime = frame.cpuTime;
256         var normalizedScale = scale;
257         if (totalCPUTime > frame.duration)
258             normalizedScale *= frame.duration / totalCPUTime;
259
260         for (var i = 0, bottomOffset = windowHeight; i < categories.length; ++i) {
261             var category = categories[i];
262             var duration = frame.timeByCategory[category];
263             if (!duration)
264                 continue;
265             var height = Math.round(duration * normalizedScale);
266             var y = Math.floor(bottomOffset - height) + 0.5;
267
268             this._context.save();
269             this._context.translate(x, 0);
270             this._context.scale(width / this._maxInnerBarWidth, 1);
271             this._context.fillStyle = this._fillStyles[category];
272             this._context.fillRect(0, y, this._maxInnerBarWidth, Math.floor(height));
273             this._context.strokeStyle = WebInspector.TimelinePresentationModel.categories()[category].borderColor;
274             this._context.beginPath();
275             this._context.moveTo(0, y);
276             this._context.lineTo(this._maxInnerBarWidth, y);
277             this._context.stroke();
278             this._context.restore();
279
280             bottomOffset -= height;
281         }
282         // Draw a contour for the total frame time.
283         var y0 = Math.floor(windowHeight - frame.duration * scale) + 0.5;
284         var y1 = windowHeight + 0.5;
285
286         this._context.strokeStyle = "rgba(90, 90, 90, 0.3)";
287         this._context.beginPath();
288         this._context.moveTo(x, y1);
289         this._context.lineTo(x, y0);
290         this._context.lineTo(x + width, y0);
291         this._context.lineTo(x + width, y1);
292         this._context.stroke();
293     },
294
295     /**
296      * @param {number} windowLeft
297      * @param {number} windowRight
298      * @return {!{startTime: number, endTime: number}}
299      */
300     windowTimes: function(windowLeft, windowRight)
301     {
302         if (!this._barTimes.length)
303             return WebInspector.TimelineOverviewBase.prototype.windowTimes.call(this, windowLeft, windowRight);
304         var windowSpan = this._canvas.width;
305         var leftOffset = windowLeft * windowSpan - this._outerPadding + this._actualPadding;
306         var rightOffset = windowRight * windowSpan - this._outerPadding;
307         var firstBar = Math.floor(Math.max(leftOffset, 0) / this._actualOuterBarWidth);
308         var lastBar = Math.min(Math.floor(rightOffset / this._actualOuterBarWidth), this._barTimes.length - 1);
309         if (firstBar >= this._barTimes.length)
310             return {startTime: Infinity, endTime: Infinity};
311
312         const snapToRightTolerancePixels = 3;
313         return {
314             startTime: this._barTimes[firstBar].startTime,
315             endTime: (rightOffset + snapToRightTolerancePixels > windowSpan) || (lastBar >= this._barTimes.length) ? Infinity : this._barTimes[lastBar].endTime
316         }
317     },
318
319     /**
320      * @param {number} startTime
321      * @param {number} endTime
322      * @return {!{left: number, right: number}}
323      */
324     windowBoundaries: function(startTime, endTime)
325     {
326         if (this._barTimes.length === 0)
327             return {left: 0, right: 1};
328         /**
329          * @param {number} time
330          * @param {!{startTime:number, endTime:number}} barTime
331          * @return {number}
332          */
333         function barStartComparator(time, barTime)
334         {
335             return time - barTime.startTime;
336         }
337         /**
338          * @param {number} time
339          * @param {!{startTime:number, endTime:number}} barTime
340          * @return {number}
341          */
342         function barEndComparator(time, barTime)
343         {
344             // We need a frame where time is in [barTime.startTime, barTime.endTime), so exclude exact matches against endTime.
345             if (time === barTime.endTime)
346                 return 1;
347             return time - barTime.endTime;
348         }
349         return {
350             left: this._windowBoundaryFromTime(startTime, barEndComparator),
351             right: this._windowBoundaryFromTime(endTime, barStartComparator)
352         }
353     },
354
355     /**
356      * @param {number} time
357      * @param {function(number, !{startTime:number, endTime:number}):number} comparator
358      */
359     _windowBoundaryFromTime: function(time, comparator)
360     {
361         if (time === Infinity)
362             return 1;
363         var index = this._firstBarAfter(time, comparator);
364         if (!index)
365             return 0;
366         return (this._barNumberToScreenPosition(index) - this._actualPadding / 2) / this._canvas.width;
367     },
368
369     /**
370      * @param {number} time
371      * @param {function(number, {startTime:number, endTime:number}):number} comparator
372      */
373     _firstBarAfter: function(time, comparator)
374     {
375         return insertionIndexForObjectInListSortedByFunction(time, this._barTimes, comparator);
376     },
377
378     __proto__: WebInspector.TimelineOverviewBase.prototype
379 }