• 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 * @param {function(string=)} showImageCallback
34 * @extends {WebInspector.HBox}
35 */
36WebInspector.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
56WebInspector.PaintProfilerView.Events = {
57    WindowChanged: "WindowChanged"
58};
59
60WebInspector.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 */
184WebInspector.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
195WebInspector.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  */
271WebInspector.LogTreeElement = function(logItem)
272{
273    TreeElement.call(this, "", logItem);
274    this._update();
275}
276
277WebInspector.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