Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / timeline / CountersGraph.js
1 /*
2  * Copyright (C) 2012 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.SplitView}
34  * @param {string} title
35  * @param {!WebInspector.TimelineModeViewDelegate} delegate
36  * @param {!WebInspector.TimelineModel} model
37  */
38 WebInspector.CountersGraph = function(title, delegate, model)
39 {
40     WebInspector.SplitView.call(this, true, false);
41
42     this.element.id = "memory-graphs-container";
43
44     this._delegate = delegate;
45     this._model = model;
46     this._calculator = new WebInspector.TimelineCalculator(this._model);
47
48     this._graphsContainer = this.mainElement();
49     this._createCurrentValuesBar();
50     this._canvasView = new WebInspector.VBoxWithResizeCallback(this._resize.bind(this));
51     this._canvasView.show(this._graphsContainer);
52     this._canvasContainer = this._canvasView.element;
53     this._canvasContainer.id = "memory-graphs-canvas-container";
54     this._canvas = this._canvasContainer.createChild("canvas");
55     this._canvas.id = "memory-counters-graph";
56
57     this._canvasContainer.addEventListener("mouseover", this._onMouseMove.bind(this), true);
58     this._canvasContainer.addEventListener("mousemove", this._onMouseMove.bind(this), true);
59     this._canvasContainer.addEventListener("mouseout", this._onMouseOut.bind(this), true);
60     this._canvasContainer.addEventListener("click", this._onClick.bind(this), true);
61     // We create extra timeline grid here to reuse its event dividers.
62     this._timelineGrid = new WebInspector.TimelineGrid();
63     this._canvasContainer.appendChild(this._timelineGrid.dividersElement);
64
65     // Populate sidebar
66     this.sidebarElement().createChild("div", "sidebar-tree sidebar-tree-section").textContent = title;
67     this._counters = [];
68     this._counterUI = [];
69 }
70
71 WebInspector.CountersGraph.prototype = {
72     _createCurrentValuesBar: function()
73     {
74         this._currentValuesBar = this._graphsContainer.createChild("div");
75         this._currentValuesBar.id = "counter-values-bar";
76     },
77
78     /**
79      * @param {string} uiName
80      * @param {string} uiValueTemplate
81      * @param {string} color
82      * @return {!WebInspector.CountersGraph.Counter}
83      */
84     createCounter: function(uiName, uiValueTemplate, color)
85     {
86         var counter = new WebInspector.CountersGraph.Counter();
87         this._counters.push(counter);
88         this._counterUI.push(new WebInspector.CountersGraph.CounterUI(this, uiName, uiValueTemplate, color, counter));
89         return counter;
90     },
91
92     /**
93      * @return {!WebInspector.View}
94      */
95     view: function()
96     {
97         return this;
98     },
99
100     dispose: function()
101     {
102     },
103
104     reset: function()
105     {
106         for (var i = 0; i < this._counters.length; ++i) {
107             this._counters[i].reset();
108             this._counterUI[i].reset();
109         }
110         this.refresh();
111     },
112
113     _resize: function()
114     {
115         var parentElement = this._canvas.parentElement;
116         this._canvas.width = parentElement.clientWidth  * window.devicePixelRatio;
117         this._canvas.height = parentElement.clientHeight * window.devicePixelRatio;
118         var timelinePaddingLeft = 15;
119         this._calculator.setDisplayWindow(timelinePaddingLeft, this._canvas.width);
120         this.refresh();
121     },
122
123     /**
124      * @param {number} startTime
125      * @param {number} endTime
126      */
127     setWindowTimes: function(startTime, endTime)
128     {
129         this._calculator.setWindow(startTime, endTime);
130         this.scheduleRefresh();
131     },
132
133     scheduleRefresh: function()
134     {
135         WebInspector.invokeOnceAfterBatchUpdate(this, this.refresh);
136     },
137
138     draw: function()
139     {
140         for (var i = 0; i < this._counters.length; ++i) {
141             this._counters[i]._calculateVisibleIndexes(this._calculator);
142             this._counters[i]._calculateXValues(this._canvas.width);
143         }
144         this._clear();
145
146         for (var i = 0; i < this._counterUI.length; i++)
147             this._counterUI[i]._drawGraph(this._canvas);
148     },
149
150     /**
151      * @param {?Event} event
152      */
153     _onClick: function(event)
154     {
155         var x = event.x - this._canvasContainer.totalOffsetLeft();
156         var minDistance = Infinity;
157         var bestTime;
158         for (var i = 0; i < this._counterUI.length; ++i) {
159             var counterUI = this._counterUI[i];
160             if (!counterUI.counter.times.length)
161                 continue;
162             var index = counterUI._recordIndexAt(x);
163             var distance = Math.abs(x * window.devicePixelRatio - counterUI.counter.x[index]);
164             if (distance < minDistance) {
165                 minDistance = distance;
166                 bestTime = counterUI.counter.times[index];
167             }
168         }
169         if (bestTime !== undefined)
170             this._revealRecordAt(bestTime);
171     },
172
173     /**
174      * @param {number} time
175      */
176     _revealRecordAt: function(time)
177     {
178         var recordToReveal;
179         /**
180          * @param {!WebInspector.TimelineModel.Record} record
181          * @return {boolean}
182          * @this {WebInspector.CountersGraph}
183          */
184         function findRecordToReveal(record)
185         {
186             if (!this._model.isVisible(record))
187                 return false;
188             if (record.startTime() <= time && time <= record.endTime()) {
189                 recordToReveal = record;
190                 return true;
191             }
192             // If there is no record containing the time than use the latest one before that time.
193             if (!recordToReveal || record.endTime() < time && recordToReveal.endTime() < record.endTime())
194                 recordToReveal = record;
195             return false;
196         }
197         this._model.forAllRecords(null, findRecordToReveal.bind(this));
198         this._delegate.select(recordToReveal ? WebInspector.TimelineSelection.fromRecord(recordToReveal) : null);
199     },
200
201     /**
202      * @param {?Event} event
203      */
204     _onMouseOut: function(event)
205     {
206         delete this._markerXPosition;
207         this._clearCurrentValueAndMarker();
208     },
209
210     _clearCurrentValueAndMarker: function()
211     {
212         for (var i = 0; i < this._counterUI.length; i++)
213             this._counterUI[i]._clearCurrentValueAndMarker();
214     },
215
216     /**
217      * @param {?Event} event
218      */
219     _onMouseMove: function(event)
220     {
221         var x = event.x - this._canvasContainer.totalOffsetLeft();
222         this._markerXPosition = x;
223         this._refreshCurrentValues();
224     },
225
226     _refreshCurrentValues: function()
227     {
228         if (this._markerXPosition === undefined)
229             return;
230         for (var i = 0; i < this._counterUI.length; ++i)
231             this._counterUI[i].updateCurrentValue(this._markerXPosition);
232     },
233
234     refresh: function()
235     {
236         this._timelineGrid.updateDividers(this._calculator);
237         this.draw();
238         this._refreshCurrentValues();
239     },
240
241     refreshRecords: function()
242     {
243     },
244
245     _clear: function()
246     {
247         var ctx = this._canvas.getContext("2d");
248         ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
249     },
250
251     /**
252      * @param {?WebInspector.TimelineModel.Record} record
253      * @param {string=} regex
254      * @param {boolean=} selectRecord
255      */
256     highlightSearchResult: function(record, regex, selectRecord)
257     {
258     },
259
260     /**
261      * @param {?WebInspector.TimelineSelection} selection
262      */
263     setSelection: function(selection)
264     {
265     },
266
267     __proto__: WebInspector.SplitView.prototype
268 }
269
270 /**
271  * @constructor
272  */
273 WebInspector.CountersGraph.Counter = function()
274 {
275     this.times = [];
276     this.values = [];
277 }
278
279 WebInspector.CountersGraph.Counter.prototype = {
280     /**
281      * @param {number} time
282      * @param {number} value
283      */
284     appendSample: function(time, value)
285     {
286         if (this.values.length && this.values.peekLast() === value)
287             return;
288         this.times.push(time);
289         this.values.push(value);
290     },
291
292     reset: function()
293     {
294         this.times = [];
295         this.values = [];
296     },
297
298     /**
299      * @param {number} value
300      */
301     setLimit: function(value)
302     {
303         this._limitValue = value;
304     },
305
306     /**
307      * @return {!{min: number, max: number}}
308      */
309     _calculateBounds: function()
310     {
311         var maxValue;
312         var minValue;
313         for (var i = this._minimumIndex; i <= this._maximumIndex; i++) {
314             var value = this.values[i];
315             if (minValue === undefined || value < minValue)
316                 minValue = value;
317             if (maxValue === undefined || value > maxValue)
318                 maxValue = value;
319         }
320         minValue = minValue || 0;
321         maxValue = maxValue || 1;
322         if (this._limitValue) {
323             if (maxValue > this._limitValue * 0.5)
324                 maxValue = Math.max(maxValue, this._limitValue);
325             minValue = Math.min(minValue, this._limitValue);
326         }
327         return { min: minValue, max: maxValue };
328     },
329
330     /**
331      * @param {!WebInspector.TimelineCalculator} calculator
332      */
333     _calculateVisibleIndexes: function(calculator)
334     {
335         var start = calculator.minimumBoundary();
336         var end = calculator.maximumBoundary();
337
338         // Maximum index of element whose time <= start.
339         this._minimumIndex = Number.constrain(this.times.upperBound(start) - 1, 0, this.times.length - 1);
340
341         // Minimum index of element whose time >= end.
342         this._maximumIndex = Number.constrain(this.times.lowerBound(end), 0, this.times.length - 1);
343
344         // Current window bounds.
345         this._minTime = start;
346         this._maxTime = end;
347     },
348
349     /**
350      * @param {number} width
351      */
352     _calculateXValues: function(width)
353     {
354         if (!this.values.length)
355             return;
356
357         var xFactor = width / (this._maxTime - this._minTime);
358
359         this.x = new Array(this.values.length);
360         for (var i = this._minimumIndex + 1; i <= this._maximumIndex; i++)
361              this.x[i] = xFactor * (this.times[i] - this._minTime);
362     }
363 }
364
365 /**
366  * @constructor
367  * @param {!WebInspector.CountersGraph} memoryCountersPane
368  * @param {string} title
369  * @param {string} currentValueLabel
370  * @param {string} graphColor
371  * @param {!WebInspector.CountersGraph.Counter} counter
372  */
373 WebInspector.CountersGraph.CounterUI = function(memoryCountersPane, title, currentValueLabel, graphColor, counter)
374 {
375     this._memoryCountersPane = memoryCountersPane;
376     this.counter = counter;
377     var container = memoryCountersPane.sidebarElement().createChild("div", "memory-counter-sidebar-info");
378     var swatchColor = graphColor;
379     this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title), swatchColor);
380     this._swatch.addEventListener(WebInspector.SwatchCheckbox.Events.Changed, this._toggleCounterGraph.bind(this));
381     container.appendChild(this._swatch.element);
382     this._range = this._swatch.element.createChild("span");
383
384     this._value = memoryCountersPane._currentValuesBar.createChild("span", "memory-counter-value");
385     this._value.style.color = graphColor;
386     this.graphColor = graphColor;
387     this.limitColor = WebInspector.Color.parse(graphColor).setAlpha(0.3).toString(WebInspector.Color.Format.RGBA);
388     this.graphYValues = [];
389     this._verticalPadding = 10;
390
391     this._currentValueLabel = currentValueLabel;
392     this._marker = memoryCountersPane._canvasContainer.createChild("div", "memory-counter-marker");
393     this._marker.style.backgroundColor = graphColor;
394     this._clearCurrentValueAndMarker();
395 }
396
397 WebInspector.CountersGraph.CounterUI.prototype = {
398     reset: function()
399     {
400         this._range.textContent = "";
401     },
402
403     /**
404      * @param {number} minValue
405      * @param {number} maxValue
406      */
407     setRange: function(minValue, maxValue)
408     {
409         this._range.textContent = WebInspector.UIString("[%.0f:%.0f]", minValue, maxValue);
410     },
411
412     _toggleCounterGraph: function(event)
413     {
414         this._value.classList.toggle("hidden", !this._swatch.checked);
415         this._memoryCountersPane.refresh();
416     },
417
418     /**
419      * @param {number} x
420      * @return {number}
421      */
422     _recordIndexAt: function(x)
423     {
424         return this.counter.x.upperBound(x * window.devicePixelRatio, null, this.counter._minimumIndex + 1, this.counter._maximumIndex + 1) - 1;
425     },
426
427     /**
428      * @param {number} x
429      */
430     updateCurrentValue: function(x)
431     {
432         if (!this.visible() || !this.counter.values.length)
433             return;
434         var index = this._recordIndexAt(x);
435         this._value.textContent = WebInspector.UIString(this._currentValueLabel, this.counter.values[index]);
436         var y = this.graphYValues[index] / window.devicePixelRatio;
437         this._marker.style.left = x + "px";
438         this._marker.style.top = y + "px";
439         this._marker.classList.remove("hidden");
440     },
441
442     _clearCurrentValueAndMarker: function()
443     {
444         this._value.textContent = "";
445         this._marker.classList.add("hidden");
446     },
447
448     /**
449      * @param {!HTMLCanvasElement} canvas
450      */
451     _drawGraph: function(canvas)
452     {
453         var ctx = canvas.getContext("2d");
454         var width = canvas.width;
455         var height = canvas.height - 2 * this._verticalPadding;
456         if (height <= 0) {
457             this.graphYValues = [];
458             return;
459         }
460         var originY = this._verticalPadding;
461         var counter = this.counter;
462         var values = counter.values;
463
464         if (!values.length)
465             return;
466
467         var bounds = counter._calculateBounds();
468         var minValue = bounds.min;
469         var maxValue = bounds.max;
470         this.setRange(minValue, maxValue);
471
472         if (!this.visible())
473             return;
474
475         var yValues = this.graphYValues;
476         var maxYRange = maxValue - minValue;
477         var yFactor = maxYRange ? height / (maxYRange) : 1;
478
479         ctx.save();
480         ctx.lineWidth = window.devicePixelRatio;
481         if (ctx.lineWidth % 2)
482             ctx.translate(0.5, 0.5);
483         ctx.beginPath();
484         var value = values[counter._minimumIndex];
485         var currentY = Math.round(originY + height - (value - minValue) * yFactor);
486         ctx.moveTo(0, currentY);
487         for (var i = counter._minimumIndex; i <= counter._maximumIndex; i++) {
488              var x = Math.round(counter.x[i]);
489              ctx.lineTo(x, currentY);
490              var currentValue = values[i];
491              if (typeof currentValue !== "undefined")
492                 value = currentValue;
493              currentY = Math.round(originY + height - (value - minValue) * yFactor);
494              ctx.lineTo(x, currentY);
495              yValues[i] = currentY;
496         }
497         yValues.length = i;
498         ctx.lineTo(width, currentY);
499         ctx.strokeStyle = this.graphColor;
500         ctx.stroke();
501         if (counter._limitValue) {
502             var limitLineY = Math.round(originY + height - (counter._limitValue - minValue) * yFactor);
503             ctx.moveTo(0, limitLineY);
504             ctx.lineTo(width, limitLineY);
505             ctx.strokeStyle = this.limitColor;
506             ctx.stroke();
507         }
508         ctx.closePath();
509         ctx.restore();
510     },
511
512     /**
513      * @return {boolean}
514      */
515     visible: function()
516     {
517         return this._swatch.checked;
518     }
519 }
520
521
522 /**
523  * @constructor
524  * @extends {WebInspector.Object}
525  */
526 WebInspector.SwatchCheckbox = function(title, color)
527 {
528     this.element = document.createElement("div");
529     this._swatch = this.element.createChild("div", "swatch");
530     this.element.createChild("span", "title").textContent = title;
531     this._color = color;
532     this.checked = true;
533
534     this.element.addEventListener("click", this._toggleCheckbox.bind(this), true);
535 }
536
537 WebInspector.SwatchCheckbox.Events = {
538     Changed: "Changed"
539 }
540
541 WebInspector.SwatchCheckbox.prototype = {
542     get checked()
543     {
544         return this._checked;
545     },
546
547     set checked(v)
548     {
549         this._checked = v;
550         if (this._checked)
551             this._swatch.style.backgroundColor = this._color;
552         else
553             this._swatch.style.backgroundColor = "";
554     },
555
556     _toggleCheckbox: function(event)
557     {
558         this.checked = !this.checked;
559         this.dispatchEventToListeners(WebInspector.SwatchCheckbox.Events.Changed);
560     },
561
562     __proto__: WebInspector.Object.prototype
563 }