Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / components / TimelineGrid.js
1 /*
2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
3  * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
4  * Copyright (C) 2009 Google Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * @constructor
33  */
34 WebInspector.TimelineGrid = function()
35 {
36     this.element = document.createElement("div");
37
38     this._dividersElement = this.element.createChild("div", "resources-dividers");
39
40     this._gridHeaderElement = document.createElement("div");
41     this._gridHeaderElement.id = "timeline-grid-header";
42     this._eventDividersElement = this._gridHeaderElement.createChild("div", "resources-event-dividers");
43     this._dividersLabelBarElement = this._gridHeaderElement.createChild("div", "resources-dividers-label-bar");
44     this.element.appendChild(this._gridHeaderElement);
45
46     this._leftCurtainElement = this.element.createChild("div", "timeline-cpu-curtain-left");
47     this._rightCurtainElement = this.element.createChild("div", "timeline-cpu-curtain-right");
48 }
49
50 /**
51  * @param {!WebInspector.TimelineGrid.Calculator} calculator
52  * @param {number} clientWidth
53  * @return {!{offsets: !Array.<number>, precision: number}}
54  */
55 WebInspector.TimelineGrid.calculateDividerOffsets = function(calculator, clientWidth)
56 {
57     const minGridSlicePx = 64; // minimal distance between grid lines.
58     const gridFreeZoneAtLeftPx = 50;
59
60     var dividersCount = clientWidth / minGridSlicePx;
61     var gridSliceTime = calculator.boundarySpan() / dividersCount;
62     var pixelsPerTime = clientWidth / calculator.boundarySpan();
63
64     // Align gridSliceTime to a nearest round value.
65     // We allow spans that fit into the formula: span = (1|2|5)x10^n,
66     // e.g.: ...  .1  .2  .5  1  2  5  10  20  50  ...
67     // After a span has been chosen make grid lines at multiples of the span.
68
69     var logGridSliceTime = Math.ceil(Math.log(gridSliceTime) / Math.LN10);
70     gridSliceTime = Math.pow(10, logGridSliceTime);
71     if (gridSliceTime * pixelsPerTime >= 5 * minGridSlicePx)
72         gridSliceTime = gridSliceTime / 5;
73     if (gridSliceTime * pixelsPerTime >= 2 * minGridSlicePx)
74         gridSliceTime = gridSliceTime / 2;
75
76     var firstDividerTime = Math.ceil((calculator.minimumBoundary() - calculator.zeroTime()) / gridSliceTime) * gridSliceTime + calculator.zeroTime();
77     var lastDividerTime = calculator.maximumBoundary();
78     // Add some extra space past the right boundary as the rightmost divider label text
79     // may be partially shown rather than just pop up when a new rightmost divider gets into the view.
80     if (calculator.paddingLeft() > 0)
81         lastDividerTime = lastDividerTime + minGridSlicePx / pixelsPerTime;
82     dividersCount = Math.ceil((lastDividerTime - firstDividerTime) / gridSliceTime);
83
84     var skipLeftmostDividers = calculator.paddingLeft() === 0;
85
86     if (!gridSliceTime)
87         dividersCount = 0;
88
89     var offsets = [];
90     for (var i = 0; i < dividersCount; ++i) {
91         var left = calculator.computePosition(firstDividerTime + gridSliceTime * i);
92         if (skipLeftmostDividers && left < gridFreeZoneAtLeftPx)
93             continue;
94         offsets.push(firstDividerTime + gridSliceTime * i);
95     }
96
97     return {offsets: offsets, precision: Math.max(0, -Math.floor(Math.log(gridSliceTime * 1.01) / Math.LN10))};
98 }
99
100 /**
101  * @param {!Object} canvas
102  * @param {!WebInspector.TimelineGrid.Calculator} calculator
103  * @param {?Array.<number>=} dividerOffsets
104  */
105 WebInspector.TimelineGrid.drawCanvasGrid = function(canvas, calculator, dividerOffsets)
106 {
107     var context = canvas.getContext("2d");
108     context.save();
109     var ratio = window.devicePixelRatio;
110     context.scale(ratio, ratio);
111     var printDeltas = !!dividerOffsets;
112     var width = canvas.width / window.devicePixelRatio;
113     var height = canvas.height / window.devicePixelRatio;
114     var precision = 0;
115     if (!dividerOffsets) {
116         var dividersData = WebInspector.TimelineGrid.calculateDividerOffsets(calculator, width);
117         dividerOffsets = dividersData.offsets;
118         precision = dividersData.precision;
119     }
120
121     context.fillStyle = "rgba(255, 255, 255, 0.5)";
122     context.fillRect(0, 0, width, 15);
123
124     context.fillStyle = "#333";
125     context.strokeStyle = "rgba(0, 0, 0, 0.1)";
126     context.textBaseline = "hanging";
127     context.font = (printDeltas ? "italic bold 11px " : " 11px ") + WebInspector.fontFamily();
128     context.lineWidth = 1;
129
130     context.translate(0.5, 0.5);
131     const minWidthForTitle = 60;
132     var lastPosition = 0;
133     var time = 0;
134     var lastTime = 0;
135     var paddingRight = 4;
136     var paddingTop = 3;
137     for (var i = 0; i < dividerOffsets.length; ++i) {
138         time = dividerOffsets[i];
139         var position = calculator.computePosition(time);
140         context.beginPath();
141         if (position - lastPosition > minWidthForTitle) {
142             if (!printDeltas || i !== 0) {
143                 var text = printDeltas ? calculator.formatTime(calculator.zeroTime() + time - lastTime) : calculator.formatTime(time, precision);
144                 var textWidth = context.measureText(text).width;
145                 var textPosition = printDeltas ? (position + lastPosition - textWidth) / 2 : position - textWidth - paddingRight;
146                 context.fillText(text, textPosition, paddingTop);
147             }
148         }
149         context.moveTo(position, 0);
150         context.lineTo(position, height);
151         context.stroke();
152         lastTime = time;
153         lastPosition = position;
154     }
155     context.restore();
156 },
157
158 WebInspector.TimelineGrid.prototype = {
159     get dividersElement()
160     {
161         return this._dividersElement;
162     },
163
164     get dividersLabelBarElement()
165     {
166         return this._dividersLabelBarElement;
167     },
168
169     removeDividers: function()
170     {
171         this._dividersElement.removeChildren();
172         this._dividersLabelBarElement.removeChildren();
173     },
174
175     /**
176      * @param {!WebInspector.TimelineGrid.Calculator} calculator
177      * @param {?Array.<number>=} dividerOffsets
178      * @param {boolean=} printDeltas
179      * @return {boolean}
180      */
181     updateDividers: function(calculator, dividerOffsets, printDeltas)
182     {
183         var precision = 0;
184         if (!dividerOffsets) {
185             var dividersData = WebInspector.TimelineGrid.calculateDividerOffsets(calculator, this._dividersElement.clientWidth);
186             dividerOffsets = dividersData.offsets;
187             precision = dividersData.precision;
188             printDeltas = false;
189         }
190
191         var dividersElementClientWidth = this._dividersElement.clientWidth;
192
193         // Reuse divider elements and labels.
194         var divider = /** @type {?Element} */ (this._dividersElement.firstChild);
195         var dividerLabelBar = /** @type {?Element} */ (this._dividersLabelBarElement.firstChild);
196
197         const minWidthForTitle = 60;
198         var lastPosition = 0;
199         var lastTime = 0;
200         for (var i = 0; i < dividerOffsets.length; ++i) {
201             if (!divider) {
202                 divider = document.createElement("div");
203                 divider.className = "resources-divider";
204                 this._dividersElement.appendChild(divider);
205
206                 dividerLabelBar = document.createElement("div");
207                 dividerLabelBar.className = "resources-divider";
208                 var label = document.createElement("div");
209                 label.className = "resources-divider-label";
210                 dividerLabelBar._labelElement = label;
211                 dividerLabelBar.appendChild(label);
212                 this._dividersLabelBarElement.appendChild(dividerLabelBar);
213             }
214
215             var time = dividerOffsets[i];
216             var position = calculator.computePosition(time);
217             if (position - lastPosition > minWidthForTitle)
218                 dividerLabelBar._labelElement.textContent = printDeltas ? calculator.formatTime(time - lastTime) : calculator.formatTime(time, precision);
219             else
220                 dividerLabelBar._labelElement.textContent = "";
221
222             if (printDeltas)
223                 dividerLabelBar._labelElement.style.width = Math.ceil(position - lastPosition) + "px";
224             else
225                 dividerLabelBar._labelElement.style.removeProperty("width");
226
227             lastPosition = position;
228             lastTime = time;
229             var percentLeft = 100 * position / dividersElementClientWidth;
230             divider.style.left = percentLeft + "%";
231             dividerLabelBar.style.left = percentLeft + "%";
232
233             divider = /** @type {?Element} */ (divider.nextSibling);
234             dividerLabelBar = /** @type {?Element} */ (dividerLabelBar.nextSibling);
235         }
236
237         // Remove extras.
238         while (divider) {
239             var nextDivider = divider.nextSibling;
240             this._dividersElement.removeChild(divider);
241             divider = nextDivider;
242         }
243         while (dividerLabelBar) {
244             var nextDivider = dividerLabelBar.nextSibling;
245             this._dividersLabelBarElement.removeChild(dividerLabelBar);
246             dividerLabelBar = nextDivider;
247         }
248         return true;
249     },
250
251     addEventDivider: function(divider)
252     {
253         this._eventDividersElement.appendChild(divider);
254     },
255
256     addEventDividers: function(dividers)
257     {
258         this._gridHeaderElement.removeChild(this._eventDividersElement);
259         for (var i = 0; i < dividers.length; ++i) {
260             if (dividers[i])
261                 this._eventDividersElement.appendChild(dividers[i]);
262         }
263         this._gridHeaderElement.appendChild(this._eventDividersElement);
264     },
265
266     removeEventDividers: function()
267     {
268         this._eventDividersElement.removeChildren();
269     },
270
271     hideEventDividers: function()
272     {
273         this._eventDividersElement.classList.add("hidden");
274     },
275
276     showEventDividers: function()
277     {
278         this._eventDividersElement.classList.remove("hidden");
279     },
280
281     hideDividers: function()
282     {
283         this._dividersElement.classList.add("hidden");
284     },
285
286     showDividers: function()
287     {
288         this._dividersElement.classList.remove("hidden");
289     },
290
291     hideCurtains: function()
292     {
293         this._leftCurtainElement.classList.add("hidden");
294         this._rightCurtainElement.classList.add("hidden");
295     },
296
297     /**
298      * @param {number} gapOffset
299      * @param {number} gapWidth
300      */
301     showCurtains: function(gapOffset, gapWidth)
302     {
303         this._leftCurtainElement.style.width = gapOffset + "px";
304         this._leftCurtainElement.classList.remove("hidden");
305         this._rightCurtainElement.style.left = (gapOffset + gapWidth) + "px";
306         this._rightCurtainElement.classList.remove("hidden");
307     },
308
309     setScrollAndDividerTop: function(scrollTop, dividersTop)
310     {
311         this._dividersLabelBarElement.style.top = scrollTop + "px";
312         this._eventDividersElement.style.top = scrollTop + "px";
313         this._leftCurtainElement.style.top = scrollTop + "px";
314         this._rightCurtainElement.style.top = scrollTop + "px";
315     }
316 }
317
318 /**
319  * @interface
320  */
321 WebInspector.TimelineGrid.Calculator = function() { }
322
323 WebInspector.TimelineGrid.Calculator.prototype = {
324     /**
325      * @return {number}
326      */
327     paddingLeft: function() { },
328
329     /**
330      * @param {number} time
331      * @return {number}
332      */
333     computePosition: function(time) { },
334
335     /**
336      * @param {number} time
337      * @param {number=} precision
338      * @return {string}
339      */
340     formatTime: function(time, precision) { },
341
342     /** @return {number} */
343     minimumBoundary: function() { },
344
345     /** @return {number} */
346     zeroTime: function() { },
347
348     /** @return {number} */
349     maximumBoundary: function() { },
350
351     /** @return {number} */
352     boundarySpan: function() { }
353 }