| /* |
| * 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 |
| */ |
| WebInspector.DOMCountersGraph = function(timelinePanel, model) |
| { |
| WebInspector.MemoryStatistics.call(this, timelinePanel, model); |
| } |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.CounterUIBase} |
| * @param {!WebInspector.DOMCountersGraph} memoryCountersPane |
| * @param {string} title |
| * @param {string} currentValueLabel |
| * @param {!string} color |
| * @param {function(!WebInspector.DOMCountersGraph.Counter):number} valueGetter |
| */ |
| WebInspector.DOMCounterUI = function(memoryCountersPane, title, currentValueLabel, color, valueGetter) |
| { |
| WebInspector.CounterUIBase.call(this, memoryCountersPane, title, color, valueGetter) |
| this._range = this._swatch.element.createChild("span"); |
| |
| this._value = memoryCountersPane._currentValuesBar.createChild("span", "memory-counter-value"); |
| this._value.style.color = color; |
| this._currentValueLabel = currentValueLabel; |
| |
| this.graphColor = color; |
| 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, usedGPUMemoryKBytes) |
| { |
| WebInspector.MemoryStatistics.Counter.call(this, time); |
| this.documentCount = documentCount; |
| this.nodeCount = nodeCount; |
| this.listenerCount = listenerCount; |
| this.usedGPUMemoryKBytes = usedGPUMemoryKBytes; |
| } |
| |
| 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.classList.add("dom-counters"); |
| }, |
| |
| /** |
| * @return {!Element} |
| */ |
| resizeElement: function() |
| { |
| return this._currentValuesBar; |
| }, |
| |
| /** |
| * @return {!Array.<!WebInspector.DOMCounterUI>} |
| */ |
| _createCounterUIList: function() |
| { |
| function getDocumentCount(entry) |
| { |
| return entry.documentCount; |
| } |
| function getNodeCount(entry) |
| { |
| return entry.nodeCount; |
| } |
| function getListenerCount(entry) |
| { |
| return entry.listenerCount; |
| } |
| function getUsedGPUMemoryKBytes(entry) |
| { |
| return entry.usedGPUMemoryKBytes || 0; |
| } |
| var counterUIs = [ |
| new WebInspector.DOMCounterUI(this, "Documents", "Documents: %d", "#d00", getDocumentCount), |
| new WebInspector.DOMCounterUI(this, "Nodes", "Nodes: %d", "#0a0", getNodeCount), |
| new WebInspector.DOMCounterUI(this, "Listeners", "Listeners: %d", "#00d", getListenerCount) |
| ]; |
| if (WebInspector.experimentsSettings.gpuTimeline.isEnabled()) |
| counterUIs.push(new WebInspector.DOMCounterUI(this, "GPU Memory", "GPU Memory [KB]: %d", "#c0c", getUsedGPUMemoryKBytes)); |
| return counterUIs; |
| }, |
| |
| /** |
| * @param {!WebInspector.Event} event |
| */ |
| _onRecordAdded: function(event) |
| { |
| /** |
| * @param {!Array.<!T>} array |
| * @param {!S} item |
| * @param {!function(!T,!S):!number} comparator |
| * @return {!number} |
| * @template T,S |
| */ |
| function findInsertionLocation(array, item, comparator) |
| { |
| var index = array.length; |
| while (index > 0 && comparator(array[index - 1], item) > 0) |
| --index; |
| return index; |
| } |
| |
| /** |
| * @this {WebInspector.DOMCountersGraph} |
| */ |
| function addStatistics(record) |
| { |
| var counters = record["counters"]; |
| var isGPURecord = record.data && typeof record.data["usedGPUMemoryBytes"] !== "undefined"; |
| if (isGPURecord) { |
| counters = counters || { |
| "startTime": record.startTime, |
| "endTime": record.endTime |
| }; |
| counters["usedGPUMemoryKBytes"] = Math.round(record.data["usedGPUMemoryBytes"] / 1024); |
| } |
| if (!counters) |
| return; |
| |
| var time = record.endTime || record.startTime; |
| var counter = new WebInspector.DOMCountersGraph.Counter( |
| time, |
| counters["documents"], |
| counters["nodes"], |
| counters["jsEventListeners"], |
| counters["usedGPUMemoryKBytes"] |
| ); |
| |
| function compare(record, time) |
| { |
| return record.time - time; |
| } |
| var index = findInsertionLocation(this._counters, time, compare); |
| this._counters.splice(index, 0, counter); |
| if (isGPURecord) { |
| // Populate missing values from preceeding records. |
| // FIXME: Refactor the code to make each WebInspector.DOMCountersGraph.Counter |
| // be responsible for a single graph to avoid such synchronizations. |
| for (var i = index - 1; i >= 0 && typeof this._counters[i].usedGPUMemoryKBytes === "undefined"; --i) { } |
| var usedGPUMemoryKBytes = this._counters[i >= 0 ? i : index].usedGPUMemoryKBytes; |
| for (i = Math.max(i, 0); i < index; ++i) |
| this._counters[i].usedGPUMemoryKBytes = usedGPUMemoryKBytes; |
| var copyFrom = index > 0 ? index - 1 : index + 1; |
| if (copyFrom < this._counters.length) { |
| this._counters[index].documentCount = this._counters[copyFrom].documentCount; |
| this._counters[index].nodeCount = this._counters[copyFrom].nodeCount; |
| this._counters[index].listenerCount = this._counters[copyFrom].listenerCount; |
| } else { |
| this._counters[index].documentCount = 0; |
| this._counters[index].nodeCount = 0; |
| this._counters[index].listenerCount = 0; |
| } |
| } |
| } |
| 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 + 0.5, y + 0.5, 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.save(); |
| ctx.translate(0.5, 0.5); |
| ctx.beginPath(); |
| var currentY = Math.round(originY + (height - (valueGetter(this._counters[this._minimumIndex]) - minValue) * yFactor)); |
| ctx.moveTo(0, currentY); |
| for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { |
| var x = Math.round(this._counters[i].x); |
| ctx.lineTo(x, currentY); |
| currentY = Math.round(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(); |
| ctx.restore(); |
| }, |
| |
| _discardImageUnderMarker: function() |
| { |
| for (var i = 0; i < this._counterUI.length; i++) |
| this._counterUI[i].discardImageUnderMarker(); |
| }, |
| |
| __proto__: WebInspector.MemoryStatistics.prototype |
| } |
| |