• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.VBox}
34 * @param {!WebInspector.TimelineModel} model
35 * @param {!WebInspector.TimelineUIUtils} uiUtils
36 */
37WebInspector.TimelineOverviewPane = function(model, uiUtils)
38{
39    WebInspector.VBox.call(this);
40    this._uiUtils = uiUtils;
41    this.element.id = "timeline-overview-pane";
42
43    this._eventDividers = [];
44
45    this._model = model;
46
47    this._overviewGrid = new WebInspector.OverviewGrid("timeline");
48    this.element.appendChild(this._overviewGrid.element);
49
50    this._overviewCalculator = new WebInspector.TimelineOverviewCalculator();
51
52    model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._reset, this);
53    this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
54    this._overviewControls = [];
55}
56
57WebInspector.TimelineOverviewPane.Events = {
58    WindowChanged: "WindowChanged"
59};
60
61WebInspector.TimelineOverviewPane.prototype = {
62    wasShown: function()
63    {
64        this.update();
65    },
66
67    onResize: function()
68    {
69        this.update();
70    },
71
72    /**
73     * @param {!Array.<!WebInspector.TimelineOverview>} overviewControls
74     */
75    setOverviewControls: function(overviewControls)
76    {
77        for (var i = 0; i < this._overviewControls.length; ++i) {
78            var overviewControl = this._overviewControls[i];
79            overviewControl.detach();
80            overviewControl.dispose();
81        }
82
83        for (var i = 0; i < overviewControls.length; ++i) {
84            overviewControls[i].setOverviewGrid(this._overviewGrid);
85            overviewControls[i].show(this._overviewGrid.element);
86        }
87        this._overviewControls = overviewControls;
88        this.update();
89    },
90
91    update: function()
92    {
93        delete this._refreshTimeout;
94
95        this._overviewCalculator._setWindow(this._model.minimumRecordTime(), this._model.maximumRecordTime());
96        this._overviewCalculator._setDisplayWindow(0, this._overviewGrid.clientWidth());
97        for (var i = 0; i < this._overviewControls.length; ++i)
98            this._overviewControls[i].update();
99        this._overviewGrid.updateDividers(this._overviewCalculator);
100        this._updateEventDividers();
101        this._updateWindow();
102    },
103
104    _updateEventDividers: function()
105    {
106        var records = this._eventDividers;
107        this._overviewGrid.removeEventDividers();
108        var dividers = [];
109        for (var i = 0; i < records.length; ++i) {
110            var record = records[i];
111            var positions = this._overviewCalculator.computeBarGraphPercentages(record);
112            var dividerPosition = Math.round(positions.start * 10);
113            if (dividers[dividerPosition])
114                continue;
115            var divider = this._uiUtils.createEventDivider(record.type());
116            divider.style.left = positions.start + "%";
117            dividers[dividerPosition] = divider;
118        }
119        this._overviewGrid.addEventDividers(dividers);
120    },
121
122    /**
123     * @param {!WebInspector.TimelineModel.Record} record
124     */
125    addRecord: function(record)
126    {
127        var eventDividers = this._eventDividers;
128        var uiUtils = this._uiUtils;
129        function addEventDividers(record)
130        {
131            if (uiUtils.isEventDivider(record))
132                eventDividers.push(record);
133        }
134        WebInspector.TimelineModel.forAllRecords([record], addEventDividers);
135        this._scheduleRefresh();
136    },
137
138    _reset: function()
139    {
140        this._overviewCalculator.reset();
141        this._overviewGrid.reset();
142        this._overviewGrid.setResizeEnabled(false);
143        this._eventDividers = [];
144        this._overviewGrid.updateDividers(this._overviewCalculator);
145        for (var i = 0; i < this._overviewControls.length; ++i)
146            this._overviewControls[i].reset();
147        this.update();
148    },
149
150    /**
151     * @param {!WebInspector.Event} event
152     */
153    _onWindowChanged: function(event)
154    {
155        if (this._muteOnWindowChanged)
156            return;
157        // Always use first control as a time converter.
158        if (!this._overviewControls.length)
159            return;
160        var windowTimes = this._overviewControls[0].windowTimes(this._overviewGrid.windowLeft(), this._overviewGrid.windowRight());
161        this._windowStartTime = windowTimes.startTime;
162        this._windowEndTime = windowTimes.endTime;
163        this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged, windowTimes);
164    },
165
166    /**
167     * @param {number} startTime
168     * @param {number} endTime
169     */
170    requestWindowTimes: function(startTime, endTime)
171    {
172        if (startTime === this._windowStartTime && endTime === this._windowEndTime)
173            return;
174        this._windowStartTime = startTime;
175        this._windowEndTime = endTime;
176        this._updateWindow();
177        this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged, { startTime: startTime, endTime: endTime });
178    },
179
180    _updateWindow: function()
181    {
182        if (!this._overviewControls.length)
183            return;
184        var windowBoundaries = this._overviewControls[0].windowBoundaries(this._windowStartTime, this._windowEndTime);
185        this._muteOnWindowChanged = true;
186        this._overviewGrid.setWindow(windowBoundaries.left, windowBoundaries.right);
187        this._overviewGrid.setResizeEnabled(!!this._model.records().length);
188        this._muteOnWindowChanged = false;
189    },
190
191    _scheduleRefresh: function()
192    {
193        if (this._refreshTimeout)
194            return;
195        if (!this.isShowing())
196            return;
197        this._refreshTimeout = setTimeout(this.update.bind(this), 300);
198    },
199
200    __proto__: WebInspector.VBox.prototype
201}
202
203/**
204 * @constructor
205 * @implements {WebInspector.TimelineGrid.Calculator}
206 */
207WebInspector.TimelineOverviewCalculator = function()
208{
209}
210
211WebInspector.TimelineOverviewCalculator.prototype = {
212    /**
213     * @return {number}
214     */
215    paddingLeft: function()
216    {
217        return this._paddingLeft;
218    },
219
220    /**
221     * @param {number} time
222     * @return {number}
223     */
224    computePosition: function(time)
225    {
226        return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this._paddingLeft;
227    },
228
229    /**
230     * @return {!{start: number, end: number}}
231     */
232    computeBarGraphPercentages: function(record)
233    {
234        var start = (record.startTime() - this._minimumBoundary) / this.boundarySpan() * 100;
235        var end = (record.endTime() - this._minimumBoundary) / this.boundarySpan() * 100;
236        return {start: start, end: end};
237    },
238
239    /**
240     * @param {number=} minimumRecordTime
241     * @param {number=} maximumRecordTime
242     */
243    _setWindow: function(minimumRecordTime, maximumRecordTime)
244    {
245        this._minimumBoundary = minimumRecordTime;
246        this._maximumBoundary = maximumRecordTime;
247    },
248
249    /**
250     * @param {number} paddingLeft
251     * @param {number} clientWidth
252     */
253    _setDisplayWindow: function(paddingLeft, clientWidth)
254    {
255        this._workingArea = clientWidth - paddingLeft;
256        this._paddingLeft = paddingLeft;
257    },
258
259    reset: function()
260    {
261        this._setWindow(0, 1000);
262    },
263
264    /**
265     * @param {number} value
266     * @param {number=} precision
267     * @return {string}
268     */
269    formatTime: function(value, precision)
270    {
271        return Number.preciseMillisToString(value - this.zeroTime(), precision);
272    },
273
274    /**
275     * @return {number}
276     */
277    maximumBoundary: function()
278    {
279        return this._maximumBoundary;
280    },
281
282    /**
283     * @return {number}
284     */
285    minimumBoundary: function()
286    {
287        return this._minimumBoundary;
288    },
289
290    /**
291     * @return {number}
292     */
293    zeroTime: function()
294    {
295        return this._minimumBoundary;
296    },
297
298    /**
299     * @return {number}
300     */
301    boundarySpan: function()
302    {
303        return this._maximumBoundary - this._minimumBoundary;
304    }
305}
306
307/**
308 * @interface
309 */
310WebInspector.TimelineOverview = function(model)
311{
312}
313
314WebInspector.TimelineOverview.prototype = {
315    /**
316     * @param {?Element} parentElement
317     * @param {!Element=} insertBefore
318     */
319    show: function(parentElement, insertBefore) { },
320
321    /**
322     * @param {!WebInspector.OverviewGrid} grid
323     */
324    setOverviewGrid: function(grid) { },
325
326    update: function() { },
327
328    dispose: function() { },
329
330    reset: function() { },
331
332    /**
333     * @param {number} windowLeft
334     * @param {number} windowRight
335     * @return {!{startTime: number, endTime: number}}
336     */
337    windowTimes: function(windowLeft, windowRight) { },
338
339    /**
340     * @param {number} startTime
341     * @param {number} endTime
342     * @return {!{left: number, right: number}}
343     */
344    windowBoundaries: function(startTime, endTime) { },
345}
346
347/**
348 * @constructor
349 * @extends {WebInspector.VBox}
350 * @implements {WebInspector.TimelineOverview}
351 * @param {!WebInspector.TimelineModel} model
352 */
353WebInspector.TimelineOverviewBase = function(model)
354{
355    WebInspector.VBox.call(this);
356
357    this._model = model;
358    this._canvas = this.element.createChild("canvas", "fill");
359    this._context = this._canvas.getContext("2d");
360}
361
362WebInspector.TimelineOverviewBase.prototype = {
363    /**
364     * @param {!WebInspector.OverviewGrid} grid
365     */
366    setOverviewGrid: function(grid)
367    {
368    },
369
370    update: function()
371    {
372        this.resetCanvas();
373    },
374
375    dispose: function()
376    {
377    },
378
379    reset: function()
380    {
381    },
382
383    timelineStarted: function()
384    {
385    },
386
387    timelineStopped: function()
388    {
389    },
390
391    /**
392     * @param {number} windowLeft
393     * @param {number} windowRight
394     * @return {!{startTime: number, endTime: number}}
395     */
396    windowTimes: function(windowLeft, windowRight)
397    {
398        var absoluteMin = this._model.minimumRecordTime();
399        var timeSpan = this._model.maximumRecordTime() - absoluteMin;
400        return {
401            startTime: absoluteMin + timeSpan * windowLeft,
402            endTime: absoluteMin + timeSpan * windowRight
403        };
404    },
405
406    /**
407     * @param {number} startTime
408     * @param {number} endTime
409     * @return {!{left: number, right: number}}
410     */
411    windowBoundaries: function(startTime, endTime)
412    {
413        var absoluteMin = this._model.minimumRecordTime();
414        var timeSpan = this._model.maximumRecordTime() - absoluteMin;
415        var haveRecords = absoluteMin > 0;
416        return {
417            left: haveRecords && startTime ? Math.min((startTime - absoluteMin) / timeSpan, 1) : 0,
418            right: haveRecords && endTime < Infinity ? (endTime - absoluteMin) / timeSpan : 1
419        }
420    },
421
422    resetCanvas: function()
423    {
424        this._canvas.width = this.element.clientWidth * window.devicePixelRatio;
425        this._canvas.height = this.element.clientHeight * window.devicePixelRatio;
426    },
427
428    __proto__: WebInspector.VBox.prototype
429}
430