Home | History | Annotate | Download | only in front_end
      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  * @param {WebInspector.TimelinePanel} timelinePanel
     33  * @param {WebInspector.TimelineModel} model
     34  * @param {number} sidebarWidth
     35  * @constructor
     36  */
     37 WebInspector.MemoryStatistics = function(timelinePanel, model, sidebarWidth)
     38 {
     39     this._timelinePanel = timelinePanel;
     40     this._counters = [];
     41 
     42     model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this);
     43     model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this);
     44 
     45     this._containerAnchor = timelinePanel.element.lastChild;
     46     this._memorySidebarView = new WebInspector.SidebarView(WebInspector.SidebarView.SidebarPosition.Start, undefined, sidebarWidth);
     47     this._memorySidebarView.sidebarElement.addStyleClass("sidebar");
     48     this._memorySidebarView.element.id = "memory-graphs-container";
     49 
     50     this._memorySidebarView.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._sidebarResized.bind(this));
     51 
     52     this._canvasContainer = this._memorySidebarView.mainElement;
     53     this._canvasContainer.id = "memory-graphs-canvas-container";
     54     this._createCurrentValuesBar();
     55     this._canvas = this._canvasContainer.createChild("canvas");
     56     this._canvas.id = "memory-counters-graph";
     57     this._lastMarkerXPosition = 0;
     58 
     59     this._canvas.addEventListener("mouseover", this._onMouseOver.bind(this), true);
     60     this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), true);
     61     this._canvas.addEventListener("mouseout", this._onMouseOut.bind(this), true);
     62     this._canvas.addEventListener("click", this._onClick.bind(this), true);
     63     // We create extra timeline grid here to reuse its event dividers.
     64     this._timelineGrid = new WebInspector.TimelineGrid();
     65     this._canvasContainer.appendChild(this._timelineGrid.dividersElement);
     66 
     67     // Populate sidebar
     68     this._memorySidebarView.sidebarElement.createChild("div", "sidebar-tree sidebar-tree-section").textContent = WebInspector.UIString("COUNTERS");
     69     this._counterUI = this._createCounterUIList();
     70 }
     71 
     72 /**
     73  * @constructor
     74  * @param {number} time
     75  */
     76 WebInspector.MemoryStatistics.Counter = function(time)
     77 {
     78     this.time = time;
     79 }
     80 
     81 /**
     82  * @constructor
     83  * @extends {WebInspector.Object}
     84  */
     85 WebInspector.SwatchCheckbox = function(title, color)
     86 {
     87     this.element = document.createElement("div");
     88     this._swatch = this.element.createChild("div", "swatch");
     89     this.element.createChild("span", "title").textContent = title;
     90     this._color = color;
     91     this.checked = true;
     92 
     93     this.element.addEventListener("click", this._toggleCheckbox.bind(this), true);
     94 }
     95 
     96 WebInspector.SwatchCheckbox.Events = {
     97     Changed: "Changed"
     98 }
     99 
    100 WebInspector.SwatchCheckbox.prototype = {
    101     get checked()
    102     {
    103         return this._checked;
    104     },
    105 
    106     set checked(v)
    107     {
    108         this._checked = v;
    109         if (this._checked)
    110             this._swatch.style.backgroundColor = this._color;
    111         else
    112             this._swatch.style.backgroundColor = "";
    113     },
    114 
    115     _toggleCheckbox: function(event)
    116     {
    117         this.checked = !this.checked;
    118         this.dispatchEventToListeners(WebInspector.SwatchCheckbox.Events.Changed);
    119     },
    120 
    121     __proto__: WebInspector.Object.prototype
    122 }
    123 
    124 /**
    125  * @constructor
    126  */
    127 WebInspector.CounterUIBase = function(memoryCountersPane, title, graphColor, valueGetter)
    128 {
    129     this._memoryCountersPane = memoryCountersPane;
    130     this.valueGetter = valueGetter;
    131     var container = memoryCountersPane._memorySidebarView.sidebarElement.createChild("div", "memory-counter-sidebar-info");
    132     var swatchColor = graphColor;
    133     this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title), swatchColor);
    134     this._swatch.addEventListener(WebInspector.SwatchCheckbox.Events.Changed, this._toggleCounterGraph.bind(this));
    135     container.appendChild(this._swatch.element);
    136 
    137     this._value = null;
    138     this.graphColor =graphColor;
    139     this.strokeColor = graphColor;
    140     this.graphYValues = [];
    141 }
    142 
    143 WebInspector.CounterUIBase.prototype = {
    144     _toggleCounterGraph: function(event)
    145     {
    146         if (this._swatch.checked)
    147             this._value.removeStyleClass("hidden");
    148         else
    149             this._value.addStyleClass("hidden");
    150         this._memoryCountersPane.refresh();
    151     },
    152 
    153     updateCurrentValue: function(countersEntry)
    154     {
    155         this._value.textContent = Number.bytesToString(this.valueGetter(countersEntry));
    156     },
    157 
    158     clearCurrentValueAndMarker: function(ctx)
    159     {
    160         this._value.textContent = "";
    161     },
    162 
    163     get visible()
    164     {
    165         return this._swatch.checked;
    166     },
    167 }
    168 
    169 WebInspector.MemoryStatistics.prototype = {
    170     _createCurrentValuesBar: function()
    171     {
    172         throw new Error("Not implemented");
    173     },
    174 
    175     _createCounterUIList: function()
    176     {
    177         throw new Error("Not implemented");
    178     },
    179 
    180     _onRecordsCleared: function()
    181     {
    182         this._counters = [];
    183     },
    184 
    185     /**
    186      * @param {WebInspector.TimelineGrid} timelineGrid
    187      */
    188     setMainTimelineGrid: function(timelineGrid)
    189     {
    190         this._mainTimelineGrid = timelineGrid;
    191     },
    192 
    193     /**
    194      * @param {number} top
    195      */
    196      setTopPosition: function(top)
    197     {
    198         this._memorySidebarView.element.style.top = top + "px";
    199         this._updateSize();
    200     },
    201 
    202     /**
    203      * @param {number} width
    204      */
    205     setSidebarWidth: function(width)
    206     {
    207         if (this._ignoreSidebarResize)
    208             return;
    209         this._ignoreSidebarResize = true;
    210         this._memorySidebarView.setSidebarWidth(width);
    211         this._ignoreSidebarResize = false;
    212     },
    213 
    214     /**
    215      * @param {WebInspector.Event} event
    216      */
    217     _sidebarResized: function(event)
    218     {
    219         if (this._ignoreSidebarResize)
    220             return;
    221         this._ignoreSidebarResize = true;
    222         this._timelinePanel.splitView.setSidebarWidth(event.data);
    223         this._ignoreSidebarResize = false;
    224     },
    225 
    226     _canvasHeight: function()
    227     {
    228         throw new Error("Not implemented");
    229     },
    230 
    231     _updateSize: function()
    232     {
    233         var width = this._mainTimelineGrid.dividersElement.offsetWidth + 1;
    234         this._canvasContainer.style.width = width + "px";
    235 
    236         var height = this._canvasHeight();
    237         this._canvas.width = width;
    238         this._canvas.height = height;
    239     },
    240 
    241     /**
    242      * @param {WebInspector.Event} event
    243      */
    244     _onRecordAdded: function(event)
    245     {
    246         throw new Error("Not implemented");
    247     },
    248 
    249     _draw: function()
    250     {
    251         this._calculateVisibleIndexes();
    252         this._calculateXValues();
    253         this._clear();
    254 
    255         this._setVerticalClip(10, this._canvas.height - 20);
    256     },
    257 
    258     _calculateVisibleIndexes: function()
    259     {
    260         var calculator = this._timelinePanel.calculator;
    261         var start = calculator.minimumBoundary() * 1000;
    262         var end = calculator.maximumBoundary() * 1000;
    263         function comparator(value, sample)
    264         {
    265             return value - sample.time;
    266         }
    267 
    268         // Maximum index of element whose time <= start.
    269         this._minimumIndex = Number.constrain(this._counters.upperBound(start, comparator) - 1, 0, this._counters.length - 1);
    270 
    271         // Minimum index of element whose time >= end.
    272         this._maximumIndex = Number.constrain(this._counters.lowerBound(end, comparator), 0, this._counters.length - 1);
    273 
    274         // Current window bounds.
    275         this._minTime = start;
    276         this._maxTime = end;
    277     },
    278 
    279     /**
    280      * @param {MouseEvent} event
    281      */
    282      _onClick: function(event)
    283     {
    284         var x = event.x - event.target.offsetParent.offsetLeft;
    285         var i = this._recordIndexAt(x);
    286         var counter = this._counters[i];
    287         if (counter)
    288             this._timelinePanel.revealRecordAt(counter.time / 1000);
    289     },
    290 
    291     /**
    292      * @param {MouseEvent} event
    293      */
    294      _onMouseOut: function(event)
    295     {
    296         delete this._markerXPosition;
    297 
    298         var ctx = this._canvas.getContext("2d");
    299         this._clearCurrentValueAndMarker(ctx);
    300     },
    301 
    302     /**
    303      * @param {CanvasRenderingContext2D} ctx
    304      */
    305     _clearCurrentValueAndMarker: function(ctx)
    306     {
    307         for (var i = 0; i < this._counterUI.length; i++)
    308             this._counterUI[i].clearCurrentValueAndMarker(ctx);
    309     },
    310 
    311     /**
    312      * @param {MouseEvent} event
    313      */
    314      _onMouseOver: function(event)
    315     {
    316         this._onMouseMove(event);
    317     },
    318 
    319     /**
    320      * @param {MouseEvent} event
    321      */
    322      _onMouseMove: function(event)
    323     {
    324         var x = event.x - event.target.offsetParent.offsetLeft
    325         this._markerXPosition = x;
    326         this._refreshCurrentValues();
    327     },
    328 
    329     _refreshCurrentValues: function()
    330     {
    331         if (!this._counters.length)
    332             return;
    333         if (this._markerXPosition === undefined)
    334             return;
    335         if (this._maximumIndex === -1)
    336             return;
    337         var i = this._recordIndexAt(this._markerXPosition);
    338 
    339         this._updateCurrentValue(this._counters[i]);
    340 
    341         this._highlightCurrentPositionOnGraphs(this._markerXPosition, i);
    342     },
    343 
    344     _updateCurrentValue: function(counterEntry)
    345     {
    346         for (var j = 0; j < this._counterUI.length; j++)
    347             this._counterUI[j].updateCurrentValue(counterEntry);
    348     },
    349 
    350     _recordIndexAt: function(x)
    351     {
    352         var i;
    353         for (i = this._minimumIndex + 1; i <= this._maximumIndex; i++) {
    354             var statX = this._counters[i].x;
    355             if (x < statX)
    356                 break;
    357         }
    358         i--;
    359         return i;
    360     },
    361 
    362     _highlightCurrentPositionOnGraphs: function(x, index)
    363     {
    364         var ctx = this._canvas.getContext("2d");
    365         this._restoreImageUnderMarker(ctx);
    366         this._drawMarker(ctx, x, index);
    367     },
    368 
    369     _restoreImageUnderMarker: function(ctx)
    370     {
    371         throw new Error("Not implemented");
    372     },
    373 
    374     _drawMarker: function(ctx, x, index)
    375     {
    376         throw new Error("Not implemented");
    377     },
    378 
    379     visible: function()
    380     {
    381         return this._memorySidebarView.isShowing();
    382     },
    383 
    384     show: function()
    385     {
    386         var anchor = /** @type {Element|null} */ (this._containerAnchor.nextSibling);
    387         var savedSidebarSize = this._timelinePanel.splitView.sidebarWidth();
    388         this._memorySidebarView.show(this._timelinePanel.element, anchor);
    389         if (savedSidebarSize > 0) {
    390             this.setSidebarWidth(savedSidebarSize);
    391             this._timelinePanel.splitView.setSidebarWidth(savedSidebarSize);
    392         }
    393         this._updateSize();
    394         this._refreshDividers();
    395         setTimeout(this._draw.bind(this), 0);
    396     },
    397 
    398     refresh: function()
    399     {
    400         this._updateSize();
    401         this._refreshDividers();
    402         this._draw();
    403         this._refreshCurrentValues();
    404     },
    405 
    406     hide: function()
    407     {
    408         this._memorySidebarView.detach();
    409     },
    410 
    411     _refreshDividers: function()
    412     {
    413         this._timelineGrid.updateDividers(this._timelinePanel.calculator);
    414     },
    415 
    416     _setVerticalClip: function(originY, height)
    417     {
    418         this._originY = originY;
    419         this._clippedHeight = height;
    420     },
    421 
    422     _calculateXValues: function()
    423     {
    424         if (!this._counters.length)
    425             return;
    426 
    427         var width = this._canvas.width;
    428         var xFactor = width / (this._maxTime - this._minTime);
    429 
    430         this._counters[this._minimumIndex].x = 0;
    431         for (var i = this._minimumIndex + 1; i < this._maximumIndex; i++)
    432              this._counters[i].x = xFactor * (this._counters[i].time - this._minTime);
    433         this._counters[this._maximumIndex].x = width;
    434     },
    435 
    436     _clear: function() {
    437         var ctx = this._canvas.getContext("2d");
    438         ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    439         this._discardImageUnderMarker();
    440     },
    441 
    442     _discardImageUnderMarker: function()
    443     {
    444         throw new Error("Not implemented");
    445     }
    446 }
    447 
    448