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