Home | History | Annotate | Download | only in layers
      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  * @param {function(string=)} showImageCallback
     34  * @extends {WebInspector.HBox}
     35  */
     36 WebInspector.PaintProfilerView = function(showImageCallback)
     37 {
     38     WebInspector.View.call(this);
     39     this.element.classList.add("paint-profiler-view");
     40 
     41     this._showImageCallback = showImageCallback;
     42 
     43     this._canvas = this.element.createChild("canvas", "fill");
     44     this._context = this._canvas.getContext("2d");
     45     this._selectionWindow = new WebInspector.OverviewGrid.Window(this.element, this.element);
     46     this._selectionWindow.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
     47 
     48     this._innerBarWidth = 4 * window.devicePixelRatio;
     49     this._minBarHeight = 4 * window.devicePixelRatio;
     50     this._barPaddingWidth = 2 * window.devicePixelRatio;
     51     this._outerBarWidth = this._innerBarWidth + this._barPaddingWidth;
     52 
     53     this._reset();
     54 }
     55 
     56 WebInspector.PaintProfilerView.Events = {
     57     WindowChanged: "WindowChanged"
     58 };
     59 
     60 WebInspector.PaintProfilerView.prototype = {
     61     onResize: function()
     62     {
     63         this._update();
     64     },
     65 
     66     /**
     67      * @param {?WebInspector.PaintProfilerSnapshot} snapshot
     68      */
     69     setSnapshot: function(snapshot)
     70     {
     71         this._reset();
     72         this._snapshot = snapshot;
     73         if (!this._snapshot) {
     74             this._update();
     75             return;
     76         }
     77         snapshot.requestImage(null, null, this._showImageCallback);
     78         snapshot.profile(onProfileDone.bind(this));
     79         /**
     80          * @param {!Array.<!LayerTreeAgent.PaintProfile>=} profiles
     81          * @this {WebInspector.PaintProfilerView}
     82          */
     83         function onProfileDone(profiles)
     84         {
     85             this._profiles = profiles;
     86             this._update();
     87         }
     88     },
     89 
     90     _update: function()
     91     {
     92         this._canvas.width = this.element.clientWidth * window.devicePixelRatio;
     93         this._canvas.height = this.element.clientHeight * window.devicePixelRatio;
     94         this._samplesPerBar = 0;
     95         if (!this._profiles || !this._profiles.length)
     96             return;
     97 
     98         var maxBars = Math.floor((this._canvas.width - 2 * this._barPaddingWidth) / this._outerBarWidth);
     99         var sampleCount = this._profiles[0].length;
    100         this._samplesPerBar = Math.ceil(sampleCount / maxBars);
    101         var barCount = Math.floor(sampleCount / this._samplesPerBar);
    102 
    103         var maxBarTime = 0;
    104         var barTimes = [];
    105         for (var i = 0, lastBarIndex = 0, lastBarTime = 0; i < sampleCount;) {
    106             for (var row = 0; row < this._profiles.length; row++)
    107                 lastBarTime += this._profiles[row][i];
    108             ++i;
    109             if (i - lastBarIndex == this._samplesPerBar || i == sampleCount) {
    110                 // Normalize by total number of samples accumulated.
    111                 lastBarTime /= this._profiles.length * (i - lastBarIndex);
    112                 barTimes.push(lastBarTime);
    113                 if (lastBarTime > maxBarTime)
    114                     maxBarTime = lastBarTime;
    115                 lastBarTime = 0;
    116                 lastBarIndex = i;
    117             }
    118         }
    119         const paddingHeight = 4 * window.devicePixelRatio;
    120         var scale = (this._canvas.height - paddingHeight - this._minBarHeight) / maxBarTime;
    121         this._context.fillStyle = "rgba(110, 180, 110, 0.7)";
    122         for (var i = 0; i < barTimes.length; ++i)
    123             this._renderBar(i, barTimes[i] * scale + this._minBarHeight);
    124     },
    125 
    126     /**
    127      * @param {number} index
    128      * @param {number} height
    129      */
    130     _renderBar: function(index, height)
    131     {
    132         var x = this._barPaddingWidth + index * this._outerBarWidth;
    133         var y = this._canvas.height - height;
    134         this._context.fillRect(x, y, this._innerBarWidth, height);
    135     },
    136 
    137     _onWindowChanged: function()
    138     {
    139         if (this._updateImageTimer)
    140             return;
    141         this._updateImageTimer = setTimeout(this._updateImage.bind(this), 100);
    142         this.dispatchEventToListeners(WebInspector.PaintProfilerView.Events.WindowChanged);
    143     },
    144 
    145     /**
    146      * @return {{left: number, right: number}}
    147      */
    148     windowBoundaries: function()
    149     {
    150         var screenLeft = this._selectionWindow.windowLeft * this._canvas.width;
    151         var screenRight = this._selectionWindow.windowRight * this._canvas.width;
    152         var barLeft = Math.floor((screenLeft - this._barPaddingWidth) / this._outerBarWidth);
    153         var barRight = Math.floor((screenRight - this._barPaddingWidth + this._innerBarWidth)/ this._outerBarWidth);
    154         var stepLeft = Math.max(0, barLeft * this._samplesPerBar);
    155         var stepRight = Math.min(barRight * this._samplesPerBar, this._profiles[0].length);
    156 
    157         return {left: stepLeft, right: stepRight};
    158     },
    159 
    160     _updateImage: function()
    161     {
    162         delete this._updateImageTimer;
    163         if (!this._profiles || !this._profiles.length)
    164             return;
    165 
    166         var window = this.windowBoundaries();
    167         this._snapshot.requestImage(window.left, window.right, this._showImageCallback);
    168     },
    169 
    170     _reset: function()
    171     {
    172         this._snapshot = null;
    173         this._profiles = null;
    174         this._selectionWindow.reset();
    175     },
    176 
    177     __proto__: WebInspector.HBox.prototype
    178 };
    179 
    180 /**
    181  * @constructor
    182  * @extends {WebInspector.VBox}
    183  */
    184 WebInspector.PaintProfilerCommandLogView = function()
    185 {
    186     WebInspector.VBox.call(this);
    187     this.setMinimumSize(100, 25);
    188     this.element.classList.add("outline-disclosure");
    189     var sidebarTreeElement = this.element.createChild("ol", "sidebar-tree");
    190     this.sidebarTree = new TreeOutline(sidebarTreeElement);
    191     this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true);
    192     this._reset();
    193 }
    194 
    195 WebInspector.PaintProfilerCommandLogView.prototype = {
    196     /**
    197      * @param {?WebInspector.PaintProfilerSnapshot} snapshot
    198      */
    199     setSnapshot: function(snapshot)
    200     {
    201         this._reset();
    202         if (!snapshot) {
    203             this.updateWindow();
    204             return;
    205         }
    206         snapshot.commandLog(onCommandLogDone.bind(this));
    207 
    208         /**
    209          * @param {!Array.<!Object>=} log
    210          * @this {WebInspector.PaintProfilerCommandLogView}
    211          */
    212         function onCommandLogDone(log)
    213         {
    214             this._log = log;
    215             this.updateWindow();
    216         }
    217     },
    218 
    219     /**
    220      * @param {number=} stepLeft
    221      * @param {number=} stepRight
    222      */
    223     updateWindow: function(stepLeft, stepRight)
    224     {
    225         var log = this._log;
    226         stepLeft = stepLeft || 0;
    227         stepRight = stepRight || log.length - 1;
    228         this.sidebarTree.removeChildren();
    229         for (var i = stepLeft; i <= stepRight; ++i) {
    230             var node = new WebInspector.LogTreeElement(log[i]);
    231             this.sidebarTree.appendChild(node);
    232         }
    233     },
    234 
    235     _reset: function()
    236     {
    237         this._log = [];
    238     },
    239 
    240     /**
    241      * @param {!Element} target
    242      * @return {!Element}
    243      */
    244     _getHoverAnchor: function(target)
    245     {
    246         return /** @type {!Element} */ (target.enclosingNodeOrSelfWithNodeName("span"));
    247     },
    248 
    249     /**
    250      * @param {!Element} element
    251      * @param {function(!WebInspector.RemoteObject, boolean, !Element=):undefined} showCallback
    252      */
    253     _resolveObjectForPopover: function(element, showCallback)
    254     {
    255         var liElement = element.enclosingNodeOrSelfWithNodeName("li");
    256         var logItem = liElement.treeElement.representedObject;
    257         var obj = {"method": logItem.method};
    258         if (logItem.params)
    259             obj.params = logItem.params;
    260         showCallback(WebInspector.RemoteObject.fromLocalObject(obj), false);
    261     },
    262 
    263     __proto__: WebInspector.VBox.prototype
    264 };
    265 
    266 /**
    267   * @constructor
    268   * @param {!Object} logItem
    269   * @extends {TreeElement}
    270   */
    271 WebInspector.LogTreeElement = function(logItem)
    272 {
    273     TreeElement.call(this, "", logItem);
    274     this._update();
    275 }
    276 
    277 WebInspector.LogTreeElement.prototype = {
    278     /**
    279       * @param {!Object} param
    280       * @param {string} name
    281       * @return {string}
    282       */
    283     _paramToString: function(param, name)
    284     {
    285         if (typeof param !== "object")
    286             return typeof param === "string" && param.length > 100 ? name : JSON.stringify(param);
    287         var str = "";
    288         var keyCount = 0;
    289         for (var key in param) {
    290             if (++keyCount > 4 || typeof param[key] === "object" || (typeof param[key] === "string" && param[key].length > 100))
    291                 return name;
    292             if (str)
    293                 str += ", ";
    294             str += param[key];
    295         }
    296         return str;
    297     },
    298 
    299     /**
    300       * @param {!Object} params
    301       * @return {string}
    302       */
    303     _paramsToString: function(params)
    304     {
    305         var str = "";
    306         for (var key in params) {
    307             if (str)
    308                 str += ", ";
    309             str += this._paramToString(params[key], key);
    310         }
    311         return str;
    312     },
    313 
    314     _update: function()
    315     {
    316         var logItem = this.representedObject;
    317         var title = document.createDocumentFragment();
    318         title.createChild("div", "selection");
    319         var span = title.createChild("span");
    320         var textContent = logItem.method;
    321         if (logItem.params)
    322             textContent += "(" + this._paramsToString(logItem.params) + ")";
    323         span.textContent = textContent;
    324         this.title = title;
    325     },
    326 
    327     /**
    328      * @param {boolean} hovered
    329      */
    330     setHovered: function(hovered)
    331     {
    332         this.listItemElement.classList.toggle("hovered", hovered);
    333     },
    334 
    335     __proto__: TreeElement.prototype
    336 };
    337