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._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 
     57 WebInspector.TimelineOverviewPane.Events = {
     58     WindowChanged: "WindowChanged"
     59 };
     60 
     61 WebInspector.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  */
    207 WebInspector.TimelineOverviewCalculator = function()
    208 {
    209 }
    210 
    211 WebInspector.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  */
    310 WebInspector.TimelineOverview = function(model)
    311 {
    312 }
    313 
    314 WebInspector.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  */
    353 WebInspector.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 
    362 WebInspector.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