UNPKG

strong-arc

Version:

A visual suite for the StrongLoop API Platform

585 lines (529 loc) 21.4 kB
/* * Copyright (C) 2013 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @constructor * @param {function(string=)} showImageCallback * @extends {WebInspector.HBox} */ WebInspector.PaintProfilerView = function(showImageCallback) { WebInspector.HBox.call(this); this.element.classList.add("paint-profiler-overview", "hbox"); this._canvasContainer = this.element.createChild("div", "paint-profiler-canvas-container"); this._pieChart = new WebInspector.PieChart(55, this._formatPieChartTime.bind(this)); this.element.createChild("div", "paint-profiler-pie-chart").appendChild(this._pieChart.element); this._showImageCallback = showImageCallback; this._canvas = this._canvasContainer.createChild("canvas", "fill"); this._context = this._canvas.getContext("2d"); this._selectionWindow = new WebInspector.OverviewGrid.Window(this._canvasContainer); this._selectionWindow.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); this._innerBarWidth = 4 * window.devicePixelRatio; this._minBarHeight = window.devicePixelRatio; this._barPaddingWidth = 2 * window.devicePixelRatio; this._outerBarWidth = this._innerBarWidth + this._barPaddingWidth; this._reset(); } WebInspector.PaintProfilerView.Events = { WindowChanged: "WindowChanged" }; WebInspector.PaintProfilerView.prototype = { onResize: function() { this._update(); }, /** * @param {?WebInspector.PaintProfilerSnapshot} snapshot * @param {!Array.<!WebInspector.PaintProfilerLogItem>} log */ setSnapshotAndLog: function(snapshot, log) { this._reset(); this._snapshot = snapshot; this._log = log; this._logCategories = this._log.map(WebInspector.PaintProfilerView._categoryForLogItem); if (!this._snapshot) { this._update(); return; } snapshot.requestImage(null, null, 1, this._showImageCallback); snapshot.profile(onProfileDone.bind(this)); /** * @param {!Array.<!LayerTreeAgent.PaintProfile>=} profiles * @this {WebInspector.PaintProfilerView} */ function onProfileDone(profiles) { this._profiles = profiles; this._update(); } }, _update: function() { this._canvas.width = this._canvasContainer.clientWidth * window.devicePixelRatio; this._canvas.height = this._canvasContainer.clientHeight * window.devicePixelRatio; this._samplesPerBar = 0; if (!this._profiles || !this._profiles.length) return; var maxBars = Math.floor((this._canvas.width - 2 * this._barPaddingWidth) / this._outerBarWidth); var sampleCount = this._log.length; this._samplesPerBar = Math.ceil(sampleCount / maxBars); var barCount = Math.floor(sampleCount / this._samplesPerBar); var maxBarTime = 0; var barTimes = []; var barHeightByCategory = []; var heightByCategory = {}; for (var i = 0, lastBarIndex = 0, lastBarTime = 0; i < sampleCount;) { var categoryName = (this._logCategories[i] && this._logCategories[i].name) || "misc"; var sampleIndex = this._log[i].commandIndex; for (var row = 0; row < this._profiles.length; row++) { var sample = this._profiles[row][sampleIndex]; lastBarTime += sample; heightByCategory[categoryName] = (heightByCategory[categoryName] || 0) + sample; } ++i; if (i - lastBarIndex == this._samplesPerBar || i == sampleCount) { // Normalize by total number of samples accumulated. var factor = this._profiles.length * (i - lastBarIndex); lastBarTime /= factor; for (categoryName in heightByCategory) heightByCategory[categoryName] /= factor; barTimes.push(lastBarTime); barHeightByCategory.push(heightByCategory); if (lastBarTime > maxBarTime) maxBarTime = lastBarTime; lastBarTime = 0; heightByCategory = {}; lastBarIndex = i; } } const paddingHeight = 4 * window.devicePixelRatio; var scale = (this._canvas.height - paddingHeight - this._minBarHeight) / maxBarTime; for (var i = 0; i < barTimes.length; ++i) { for (var categoryName in barHeightByCategory[i]) barHeightByCategory[i][categoryName] *= (barTimes[i] * scale + this._minBarHeight) / barTimes[i]; this._renderBar(i, barHeightByCategory[i]); } }, /** * @param {number} index * @param {!Object.<string, number>} heightByCategory */ _renderBar: function(index, heightByCategory) { var categories = WebInspector.PaintProfilerView.categories(); var currentHeight = 0; var x = this._barPaddingWidth + index * this._outerBarWidth; for (var categoryName in categories) { if (!heightByCategory[categoryName]) continue; currentHeight += heightByCategory[categoryName]; var y = this._canvas.height - currentHeight; this._context.fillStyle = categories[categoryName].color; this._context.fillRect(x, y, this._innerBarWidth, heightByCategory[categoryName]); } }, _onWindowChanged: function() { this.dispatchEventToListeners(WebInspector.PaintProfilerView.Events.WindowChanged); // Update pie chart var window = this.windowBoundaries(); var totalTime = 0; var timeByCategory = {}; for (var i = window.left; i <= window.right; ++i) { var logEntry = this._log[i]; var category = WebInspector.PaintProfilerView._categoryForLogItem(logEntry); timeByCategory[category.color] = timeByCategory[category.color] || 0; for (var j = 0; j < this._profiles.length; ++j) { var time = this._profiles[j][logEntry.commandIndex]; totalTime += time; timeByCategory[category.color] += time; } } this._pieChart.setTotal(totalTime / this._profiles.length); for (var color in timeByCategory) this._pieChart.addSlice(timeByCategory[color] / this._profiles.length, color); if (this._updateImageTimer) return; this._updateImageTimer = setTimeout(this._updateImage.bind(this), 100); }, /** * @param {number} value * @return {string} */ _formatPieChartTime: function(value) { return Number.millisToString(value * 1000, true); }, /** * @return {{left: number, right: number}} */ windowBoundaries: function() { var screenLeft = this._selectionWindow.windowLeft * this._canvas.width; var screenRight = this._selectionWindow.windowRight * this._canvas.width; var barLeft = Math.floor((screenLeft - this._barPaddingWidth) / this._outerBarWidth); var barRight = Math.floor((screenRight - this._barPaddingWidth + this._innerBarWidth)/ this._outerBarWidth); var stepLeft = Number.constrain(barLeft * this._samplesPerBar, 0, this._log.length - 1); var stepRight = Number.constrain(barRight * this._samplesPerBar, 0, this._log.length - 1); return { left: stepLeft, right: stepRight }; }, _updateImage: function() { delete this._updateImageTimer; if (!this._profiles || !this._profiles.length) return; var window = this.windowBoundaries(); this._snapshot.requestImage(this._log[window.left].commandIndex, this._log[window.right].commandIndex, 1, this._showImageCallback); }, _reset: function() { this._snapshot = null; this._profiles = null; this._selectionWindow.reset(); }, __proto__: WebInspector.HBox.prototype }; /** * @constructor * @extends {WebInspector.VBox} */ WebInspector.PaintProfilerCommandLogView = function() { WebInspector.VBox.call(this); this.setMinimumSize(100, 25); this.element.classList.add("outline-disclosure", "profiler-log-view", "section"); var sidebarTreeElement = this.element.createChild("ol", "sidebar-tree properties monospace"); sidebarTreeElement.addEventListener("mousemove", this._onMouseMove.bind(this), false); sidebarTreeElement.addEventListener("mouseout", this._onMouseMove.bind(this), false); sidebarTreeElement.addEventListener("contextmenu", this._onContextMenu.bind(this), true); this.sidebarTree = new TreeOutline(sidebarTreeElement); this._reset(); } WebInspector.PaintProfilerCommandLogView.prototype = { /** * @param {?WebInspector.Target} target * @param {!Array.<!WebInspector.PaintProfilerLogItem>=} log */ setCommandLog: function(target, log) { this._target = target; this._log = log; this.updateWindow(); }, /** * @param {!TreeOutline} treeOutline * @param {!WebInspector.PaintProfilerLogItem} logItem */ _appendLogItem: function(treeOutline, logItem) { var treeElement = new WebInspector.LogTreeElement(this, logItem); treeOutline.appendChild(treeElement); }, /** * @param {number=} stepLeft * @param {number=} stepRight */ updateWindow: function(stepLeft, stepRight) { stepLeft = stepLeft || 0; stepRight = stepRight || this._log.length - 1; this.sidebarTree.removeChildren(); for (var i = stepLeft; i <= stepRight; ++i) this._appendLogItem(this.sidebarTree, this._log[i]); }, _reset: function() { this._log = []; }, /** * @param {?Event} event */ _onMouseMove: function(event) { var node = this.sidebarTree.treeElementFromPoint(event.pageX, event.pageY); if (node === this._lastHoveredNode || !(node instanceof WebInspector.LogTreeElement)) return; if (this._lastHoveredNode) this._lastHoveredNode.setHovered(false); this._lastHoveredNode = node; if (this._lastHoveredNode) this._lastHoveredNode.setHovered(true); }, /** * @param {?Event} event */ _onContextMenu: function(event) { if (!this._target) return; var node = this.sidebarTree.treeElementFromPoint(event.pageX, event.pageY); if (!node || !node.representedObject || !(node instanceof WebInspector.LogTreeElement)) return; var logItem = /** @type {!WebInspector.PaintProfilerLogItem} */ (node.representedObject); if (!logItem.nodeId()) return; var contextMenu = new WebInspector.ContextMenu(event); var domNode = new WebInspector.DeferredDOMNode(this._target, logItem.nodeId()); contextMenu.appendApplicableItems(domNode); contextMenu.show(); }, __proto__: WebInspector.VBox.prototype }; /** * @constructor * @param {!WebInspector.PaintProfilerCommandLogView} ownerView * @param {!WebInspector.PaintProfilerLogItem} logItem * @extends {TreeElement} */ WebInspector.LogTreeElement = function(ownerView, logItem) { TreeElement.call(this, "", logItem); this._ownerView = ownerView; this._filled = false; } WebInspector.LogTreeElement.prototype = { onattach: function() { this._update(); this.hasChildren = !!this.representedObject.params; }, onexpand: function() { if (this._filled) return; this._filled = true; for (var param in this.representedObject.params) WebInspector.LogPropertyTreeElement._appendLogPropertyItem(this, param, this.representedObject.params[param]); }, /** * @param {!Object} param * @param {string} name * @return {string} */ _paramToString: function(param, name) { if (typeof param !== "object") return typeof param === "string" && param.length > 100 ? name : JSON.stringify(param); var str = ""; var keyCount = 0; for (var key in param) { if (++keyCount > 4 || typeof param[key] === "object" || (typeof param[key] === "string" && param[key].length > 100)) return name; if (str) str += ", "; str += param[key]; } return str; }, /** * @param {!Object} params * @return {string} */ _paramsToString: function(params) { var str = ""; for (var key in params) { if (str) str += ", "; str += this._paramToString(params[key], key); } return str; }, _update: function() { var logItem = this.representedObject; var title = document.createDocumentFragment(); title.createChild("div", "selection"); title.createTextChild(logItem.method + "(" + this._paramsToString(logItem.params) + ")"); this.title = title; }, /** * @param {boolean} hovered */ setHovered: function(hovered) { this.listItemElement.classList.toggle("hovered", hovered); var target = this._ownerView._target; if (!target) return; if (!hovered) { target.domModel.hideDOMNodeHighlight(); return; } var logItem = /** @type {!WebInspector.PaintProfilerLogItem} */ (this.representedObject); if (!logItem) return; var backendNodeId = logItem.nodeId(); if (!backendNodeId) return; new WebInspector.DeferredDOMNode(target, backendNodeId).resolve(highlightNode); /** * @param {?WebInspector.DOMNode} node */ function highlightNode(node) { if (node) node.highlight(); } }, __proto__: TreeElement.prototype }; /** * @constructor * @param {!{name: string, value}} property * @extends {TreeElement} */ WebInspector.LogPropertyTreeElement = function(property) { TreeElement.call(this, "", property); }; /** * @param {!TreeElement} element * @param {string} name * @param {*} value */ WebInspector.LogPropertyTreeElement._appendLogPropertyItem = function(element, name, value) { var treeElement = new WebInspector.LogPropertyTreeElement({name: name, value: value}); element.appendChild(treeElement); if (value && typeof value === "object") { for (var property in value) WebInspector.LogPropertyTreeElement._appendLogPropertyItem(treeElement, property, value[property]); } }; WebInspector.LogPropertyTreeElement.prototype = { onattach: function() { var property = this.representedObject; var title = document.createDocumentFragment(); title.createChild("div", "selection"); var nameElement = title.createChild("span", "name"); nameElement.textContent = property.name; var separatorElement = title.createChild("span", "separator"); separatorElement.textContent = ": "; if (property.value === null || typeof property.value !== "object") { var valueElement = title.createChild("span", "value"); valueElement.textContent = JSON.stringify(property.value); valueElement.classList.add("console-formatted-" + property.value === null ? "null" : typeof property.value); } this.title = title; }, __proto__: TreeElement.prototype } /** * @return {!Object.<string, !WebInspector.PaintProfilerCategory>} */ WebInspector.PaintProfilerView.categories = function() { if (WebInspector.PaintProfilerView._categories) return WebInspector.PaintProfilerView._categories; WebInspector.PaintProfilerView._categories = { shapes: new WebInspector.PaintProfilerCategory("shapes", WebInspector.UIString("Shapes"), "rgb(255, 161, 129)"), bitmap: new WebInspector.PaintProfilerCategory("bitmap", WebInspector.UIString("Bitmap"), "rgb(136, 196, 255)"), text: new WebInspector.PaintProfilerCategory("text", WebInspector.UIString("Text"), "rgb(180, 255, 137)"), misc: new WebInspector.PaintProfilerCategory("misc", WebInspector.UIString("Misc"), "rgb(206, 160, 255)") }; return WebInspector.PaintProfilerView._categories; }; /** * @constructor * @param {string} name * @param {string} title * @param {string} color */ WebInspector.PaintProfilerCategory = function(name, title, color) { this.name = name; this.title = title; this.color = color; } /** * @return {!Object.<string, !WebInspector.PaintProfilerCategory>} */ WebInspector.PaintProfilerView._initLogItemCategories = function() { if (WebInspector.PaintProfilerView._logItemCategoriesMap) return WebInspector.PaintProfilerView._logItemCategoriesMap; var categories = WebInspector.PaintProfilerView.categories(); var logItemCategories = {}; logItemCategories["Clear"] = categories["misc"]; logItemCategories["DrawPaint"] = categories["misc"]; logItemCategories["DrawData"] = categories["misc"]; logItemCategories["SetMatrix"] = categories["misc"]; logItemCategories["PushCull"] = categories["misc"]; logItemCategories["PopCull"] = categories["misc"]; logItemCategories["Translate"] = categories["misc"]; logItemCategories["Scale"] = categories["misc"]; logItemCategories["Concat"] = categories["misc"]; logItemCategories["Restore"] = categories["misc"]; logItemCategories["SaveLayer"] = categories["misc"]; logItemCategories["Save"] = categories["misc"]; logItemCategories["BeginCommentGroup"] = categories["misc"]; logItemCategories["AddComment"] = categories["misc"]; logItemCategories["EndCommentGroup"] = categories["misc"]; logItemCategories["ClipRect"] = categories["misc"]; logItemCategories["ClipRRect"] = categories["misc"]; logItemCategories["ClipPath"] = categories["misc"]; logItemCategories["ClipRegion"] = categories["misc"]; logItemCategories["DrawPoints"] = categories["shapes"]; logItemCategories["DrawRect"] = categories["shapes"]; logItemCategories["DrawOval"] = categories["shapes"]; logItemCategories["DrawRRect"] = categories["shapes"]; logItemCategories["DrawPath"] = categories["shapes"]; logItemCategories["DrawVertices"] = categories["shapes"]; logItemCategories["DrawDRRect"] = categories["shapes"]; logItemCategories["DrawBitmap"] = categories["bitmap"]; logItemCategories["DrawBitmapRectToRect"] = categories["bitmap"]; logItemCategories["DrawBitmapMatrix"] = categories["bitmap"]; logItemCategories["DrawBitmapNine"] = categories["bitmap"]; logItemCategories["DrawSprite"] = categories["bitmap"]; logItemCategories["DrawPicture"] = categories["bitmap"]; logItemCategories["DrawText"] = categories["text"]; logItemCategories["DrawPosText"] = categories["text"]; logItemCategories["DrawPosTextH"] = categories["text"]; logItemCategories["DrawTextOnPath"] = categories["text"]; WebInspector.PaintProfilerView._logItemCategoriesMap = logItemCategories; return logItemCategories; } /** * @param {!Object} logItem * @return {!WebInspector.PaintProfilerCategory} */ WebInspector.PaintProfilerView._categoryForLogItem = function(logItem) { var method = logItem.method.toTitleCase(); var logItemCategories = WebInspector.PaintProfilerView._initLogItemCategories(); var result = logItemCategories[method]; if (!result) { result = WebInspector.PaintProfilerView.categories()["misc"]; logItemCategories[method] = result; } return result; }