/* * 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 * @extends {WebInspector.MemoryStatistics} * @param {WebInspector.TimelinePanel} timelinePanel * @param {WebInspector.TimelineModel} model * @param {number} sidebarWidth */ WebInspector.DOMCountersGraph = function(timelinePanel, model, sidebarWidth) { WebInspector.MemoryStatistics.call(this, timelinePanel, model, sidebarWidth); } /** * @constructor * @extends {WebInspector.CounterUIBase} * @param {WebInspector.DOMCountersGraph} memoryCountersPane * @param {string} title * @param {string} currentValueLabel * @param {Array.} rgb * @param {function(WebInspector.DOMCountersGraph.Counter):number} valueGetter */ WebInspector.DOMCounterUI = function(memoryCountersPane, title, currentValueLabel, rgb, valueGetter) { var swatchColor = "rgb(" + rgb.join(",") + ")"; WebInspector.CounterUIBase.call(this, memoryCountersPane, title, swatchColor, valueGetter) this._range = this._swatch.element.createChild("span"); this._value = memoryCountersPane._currentValuesBar.createChild("span", "memory-counter-value"); this._value.style.color = swatchColor; this._currentValueLabel = currentValueLabel; this.graphColor = "rgba(" + rgb.join(",") + ",0.8)"; this.graphYValues = []; } /** * @constructor * @extends {WebInspector.MemoryStatistics.Counter} * @param {number} time * @param {number} documentCount * @param {number} nodeCount * @param {number} listenerCount */ WebInspector.DOMCountersGraph.Counter = function(time, documentCount, nodeCount, listenerCount) { WebInspector.MemoryStatistics.Counter.call(this, time); this.documentCount = documentCount; this.nodeCount = nodeCount; this.listenerCount = listenerCount; } WebInspector.DOMCounterUI.prototype = { /** * @param {number} minValue * @param {number} maxValue */ setRange: function(minValue, maxValue) { this._range.textContent = WebInspector.UIString("[ %d - %d ]", minValue, maxValue); }, updateCurrentValue: function(countersEntry) { this._value.textContent = WebInspector.UIString(this._currentValueLabel, this.valueGetter(countersEntry)); }, clearCurrentValueAndMarker: function(ctx) { this._value.textContent = ""; this.restoreImageUnderMarker(ctx); }, /** * @param {CanvasRenderingContext2D} ctx * @param {number} x * @param {number} y * @param {number} radius */ saveImageUnderMarker: function(ctx, x, y, radius) { const w = radius + 1; var imageData = ctx.getImageData(x - w, y - w, 2 * w, 2 * w); this._imageUnderMarker = { x: x - w, y: y - w, imageData: imageData }; }, /** * @param {CanvasRenderingContext2D} ctx */ restoreImageUnderMarker: function(ctx) { if (!this.visible) return; if (this._imageUnderMarker) ctx.putImageData(this._imageUnderMarker.imageData, this._imageUnderMarker.x, this._imageUnderMarker.y); this.discardImageUnderMarker(); }, discardImageUnderMarker: function() { delete this._imageUnderMarker; }, __proto__: WebInspector.CounterUIBase.prototype } WebInspector.DOMCountersGraph.prototype = { _createCurrentValuesBar: function() { this._currentValuesBar = this._canvasContainer.createChild("div"); this._currentValuesBar.id = "counter-values-bar"; this._canvasContainer.addStyleClass("dom-counters"); }, /** * @return {Array.} */ _createCounterUIList: function() { function getDocumentCount(entry) { return entry.documentCount; } function getNodeCount(entry) { return entry.nodeCount; } function getListenerCount(entry) { return entry.listenerCount; } return [ new WebInspector.DOMCounterUI(this, "Document Count", "Documents: %d", [100, 0, 0], getDocumentCount), new WebInspector.DOMCounterUI(this, "DOM Node Count", "Nodes: %d", [0, 100, 0], getNodeCount), new WebInspector.DOMCounterUI(this, "Event Listener Count", "Listeners: %d", [0, 0, 100], getListenerCount) ]; }, _canvasHeight: function() { return this._canvasContainer.offsetHeight - this._currentValuesBar.offsetHeight; }, /** * @param {WebInspector.Event} event */ _onRecordAdded: function(event) { function addStatistics(record) { var counters = record["counters"]; if (!counters) return; this._counters.push(new WebInspector.DOMCountersGraph.Counter( record.endTime || record.startTime, counters["documents"], counters["nodes"], counters["jsEventListeners"] )); } WebInspector.TimelinePresentationModel.forAllRecords([event.data], null, addStatistics.bind(this)); }, _draw: function() { WebInspector.MemoryStatistics.prototype._draw.call(this); for (var i = 0; i < this._counterUI.length; i++) this._drawGraph(this._counterUI[i]); }, /** * @param {CanvasRenderingContext2D} ctx */ _restoreImageUnderMarker: function(ctx) { for (var i = 0; i < this._counterUI.length; i++) { var counterUI = this._counterUI[i]; if (!counterUI.visible) continue; counterUI.restoreImageUnderMarker(ctx); } }, /** * @param {CanvasRenderingContext2D} ctx * @param {number} x * @param {number} index */ _saveImageUnderMarker: function(ctx, x, index) { const radius = 2; for (var i = 0; i < this._counterUI.length; i++) { var counterUI = this._counterUI[i]; if (!counterUI.visible) continue; var y = counterUI.graphYValues[index]; counterUI.saveImageUnderMarker(ctx, x, y, radius); } }, /** * @param {CanvasRenderingContext2D} ctx * @param {number} x * @param {number} index */ _drawMarker: function(ctx, x, index) { this._saveImageUnderMarker(ctx, x, index); const radius = 2; for (var i = 0; i < this._counterUI.length; i++) { var counterUI = this._counterUI[i]; if (!counterUI.visible) continue; var y = counterUI.graphYValues[index]; ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2, true); ctx.lineWidth = 1; ctx.fillStyle = counterUI.graphColor; ctx.strokeStyle = counterUI.graphColor; ctx.fill(); ctx.stroke(); ctx.closePath(); } }, /** * @param {WebInspector.CounterUIBase} counterUI */ _drawGraph: function(counterUI) { var canvas = this._canvas; var ctx = canvas.getContext("2d"); var width = canvas.width; var height = this._clippedHeight; var originY = this._originY; var valueGetter = counterUI.valueGetter; if (!this._counters.length) return; var maxValue; var minValue; for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { var value = valueGetter(this._counters[i]); if (minValue === undefined || value < minValue) minValue = value; if (maxValue === undefined || value > maxValue) maxValue = value; } counterUI.setRange(minValue, maxValue); if (!counterUI.visible) return; var yValues = counterUI.graphYValues; yValues.length = this._counters.length; var maxYRange = maxValue - minValue; var yFactor = maxYRange ? height / (maxYRange) : 1; ctx.beginPath(); var currentY = originY + (height - (valueGetter(this._counters[this._minimumIndex]) - minValue) * yFactor); ctx.moveTo(0, currentY); for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { var x = this._counters[i].x; ctx.lineTo(x, currentY); currentY = originY + (height - (valueGetter(this._counters[i]) - minValue) * yFactor); ctx.lineTo(x, currentY); yValues[i] = currentY; } ctx.lineTo(width, currentY); ctx.lineWidth = 1; ctx.strokeStyle = counterUI.graphColor; ctx.stroke(); ctx.closePath(); }, _discardImageUnderMarker: function() { for (var i = 0; i < this._counterUI.length; i++) this._counterUI[i].discardImageUnderMarker(); }, __proto__: WebInspector.MemoryStatistics.prototype }