| |
| |
| |
| |
| |
| |
| WebInspector.MemoryStatistics = function(timelinePanel, model, sidebarWidth) |
| { |
| this._timelinePanel = timelinePanel; |
| this._counters = []; |
| |
| model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this); |
| model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this); |
| |
| this._containerAnchor = timelinePanel.element.lastChild; |
| this._memorySidebarView = new WebInspector.SidebarView(WebInspector.SidebarView.SidebarPosition.Start, undefined, sidebarWidth); |
| this._memorySidebarView.sidebarElement.addStyleClass("sidebar"); |
| this._memorySidebarView.element.id = "memory-graphs-container"; |
| |
| this._memorySidebarView.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._sidebarResized.bind(this)); |
| |
| this._canvasContainer = this._memorySidebarView.mainElement; |
| this._canvasContainer.id = "memory-graphs-canvas-container"; |
| this._createCurrentValuesBar(); |
| this._canvas = this._canvasContainer.createChild("canvas"); |
| this._canvas.id = "memory-counters-graph"; |
| this._lastMarkerXPosition = 0; |
| |
| this._canvas.addEventListener("mouseover", this._onMouseOver.bind(this), true); |
| this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), true); |
| this._canvas.addEventListener("mouseout", this._onMouseOut.bind(this), true); |
| this._canvas.addEventListener("click", this._onClick.bind(this), true); |
| |
| this._timelineGrid = new WebInspector.TimelineGrid(); |
| this._canvasContainer.appendChild(this._timelineGrid.dividersElement); |
| |
| |
| this._memorySidebarView.sidebarElement.createChild("div", "sidebar-tree sidebar-tree-section").textContent = WebInspector.UIString("COUNTERS"); |
| this._counterUI = this._createCounterUIList(); |
| } |
| |
| |
| WebInspector.MemoryStatistics.Counter = function(time) |
| { |
| this.time = time; |
| } |
| |
| |
| WebInspector.SwatchCheckbox = function(title, color) |
| { |
| this.element = document.createElement("div"); |
| this._swatch = this.element.createChild("div", "swatch"); |
| this.element.createChild("span", "title").textContent = title; |
| this._color = color; |
| this.checked = true; |
| |
| this.element.addEventListener("click", this._toggleCheckbox.bind(this), true); |
| } |
| |
| WebInspector.SwatchCheckbox.Events = { |
| Changed: "Changed" |
| } |
| |
| WebInspector.SwatchCheckbox.prototype = { |
| get checked() |
| { |
| return this._checked; |
| }, |
| |
| set checked(v) |
| { |
| this._checked = v; |
| if (this._checked) |
| this._swatch.style.backgroundColor = this._color; |
| else |
| this._swatch.style.backgroundColor = ""; |
| }, |
| |
| _toggleCheckbox: function(event) |
| { |
| this.checked = !this.checked; |
| this.dispatchEventToListeners(WebInspector.SwatchCheckbox.Events.Changed); |
| }, |
| |
| __proto__: WebInspector.Object.prototype |
| } |
| |
| |
| WebInspector.CounterUIBase = function(memoryCountersPane, title, graphColor, valueGetter) |
| { |
| this._memoryCountersPane = memoryCountersPane; |
| this.valueGetter = valueGetter; |
| var container = memoryCountersPane._memorySidebarView.sidebarElement.createChild("div", "memory-counter-sidebar-info"); |
| var swatchColor = graphColor; |
| this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title), swatchColor); |
| this._swatch.addEventListener(WebInspector.SwatchCheckbox.Events.Changed, this._toggleCounterGraph.bind(this)); |
| container.appendChild(this._swatch.element); |
| |
| this._value = null; |
| this.graphColor =graphColor; |
| this.strokeColor = graphColor; |
| this.graphYValues = []; |
| } |
| |
| WebInspector.CounterUIBase.prototype = { |
| _toggleCounterGraph: function(event) |
| { |
| if (this._swatch.checked) |
| this._value.removeStyleClass("hidden"); |
| else |
| this._value.addStyleClass("hidden"); |
| this._memoryCountersPane.refresh(); |
| }, |
| |
| updateCurrentValue: function(countersEntry) |
| { |
| this._value.textContent = Number.bytesToString(this.valueGetter(countersEntry)); |
| }, |
| |
| clearCurrentValueAndMarker: function(ctx) |
| { |
| this._value.textContent = ""; |
| }, |
| |
| get visible() |
| { |
| return this._swatch.checked; |
| }, |
| } |
| |
| WebInspector.MemoryStatistics.prototype = { |
| _createCurrentValuesBar: function() |
| { |
| throw new Error("Not implemented"); |
| }, |
| |
| _createCounterUIList: function() |
| { |
| throw new Error("Not implemented"); |
| }, |
| |
| _onRecordsCleared: function() |
| { |
| this._counters = []; |
| }, |
| |
| |
| setMainTimelineGrid: function(timelineGrid) |
| { |
| this._mainTimelineGrid = timelineGrid; |
| }, |
| |
| |
| setTopPosition: function(top) |
| { |
| this._memorySidebarView.element.style.top = top + "px"; |
| this._updateSize(); |
| }, |
| |
| |
| setSidebarWidth: function(width) |
| { |
| if (this._ignoreSidebarResize) |
| return; |
| this._ignoreSidebarResize = true; |
| this._memorySidebarView.setSidebarWidth(width); |
| this._ignoreSidebarResize = false; |
| }, |
| |
| |
| _sidebarResized: function(event) |
| { |
| if (this._ignoreSidebarResize) |
| return; |
| this._ignoreSidebarResize = true; |
| this._timelinePanel.splitView.setSidebarWidth(event.data); |
| this._ignoreSidebarResize = false; |
| }, |
| |
| _canvasHeight: function() |
| { |
| throw new Error("Not implemented"); |
| }, |
| |
| _updateSize: function() |
| { |
| var width = this._mainTimelineGrid.dividersElement.offsetWidth + 1; |
| this._canvasContainer.style.width = width + "px"; |
| |
| var height = this._canvasHeight(); |
| this._canvas.width = width; |
| this._canvas.height = height; |
| }, |
| |
| |
| _onRecordAdded: function(event) |
| { |
| throw new Error("Not implemented"); |
| }, |
| |
| _draw: function() |
| { |
| this._calculateVisibleIndexes(); |
| this._calculateXValues(); |
| this._clear(); |
| |
| this._setVerticalClip(10, this._canvas.height - 20); |
| }, |
| |
| _calculateVisibleIndexes: function() |
| { |
| var calculator = this._timelinePanel.calculator; |
| var start = calculator.minimumBoundary() * 1000; |
| var end = calculator.maximumBoundary() * 1000; |
| var firstIndex = 0; |
| var lastIndex = this._counters.length - 1; |
| for (var i = 0; i < this._counters.length; i++) { |
| var time = this._counters[i].time; |
| if (time <= start) { |
| firstIndex = i; |
| } else { |
| if (end < time) |
| break; |
| lastIndex = i; |
| } |
| } |
| |
| this._minimumIndex = firstIndex; |
| |
| |
| this._maximumIndex = lastIndex; |
| |
| |
| this._minTime = start; |
| this._maxTime = end; |
| }, |
| |
| |
| _onClick: function(event) |
| { |
| var x = event.x - event.target.offsetParent.offsetLeft; |
| var i = this._recordIndexAt(x); |
| var counter = this._counters[i]; |
| if (counter) |
| this._timelinePanel.revealRecordAt(counter.time / 1000); |
| }, |
| |
| |
| _onMouseOut: function(event) |
| { |
| delete this._markerXPosition; |
| |
| var ctx = this._canvas.getContext("2d"); |
| this._clearCurrentValueAndMarker(ctx); |
| }, |
| |
| |
| _clearCurrentValueAndMarker: function(ctx) |
| { |
| for (var i = 0; i < this._counterUI.length; i++) |
| this._counterUI[i].clearCurrentValueAndMarker(ctx); |
| }, |
| |
| |
| _onMouseOver: function(event) |
| { |
| this._onMouseMove(event); |
| }, |
| |
| |
| _onMouseMove: function(event) |
| { |
| var x = event.x - event.target.offsetParent.offsetLeft |
| this._markerXPosition = x; |
| this._refreshCurrentValues(); |
| }, |
| |
| _refreshCurrentValues: function() |
| { |
| if (!this._counters.length) |
| return; |
| if (this._markerXPosition === undefined) |
| return; |
| if (this._maximumIndex === -1) |
| return; |
| var i = this._recordIndexAt(this._markerXPosition); |
| |
| this._updateCurrentValue(this._counters[i]); |
| |
| this._highlightCurrentPositionOnGraphs(this._markerXPosition, i); |
| }, |
| |
| _updateCurrentValue: function(counterEntry) |
| { |
| for (var j = 0; j < this._counterUI.length; j++) |
| this._counterUI[j].updateCurrentValue(counterEntry); |
| }, |
| |
| _recordIndexAt: function(x) |
| { |
| var i; |
| for (i = this._minimumIndex + 1; i <= this._maximumIndex; i++) { |
| var statX = this._counters[i].x; |
| if (x < statX) |
| break; |
| } |
| i--; |
| return i; |
| }, |
| |
| _highlightCurrentPositionOnGraphs: function(x, index) |
| { |
| var ctx = this._canvas.getContext("2d"); |
| this._restoreImageUnderMarker(ctx); |
| this._drawMarker(ctx, x, index); |
| }, |
| |
| _restoreImageUnderMarker: function(ctx) |
| { |
| throw new Error("Not implemented"); |
| }, |
| |
| _drawMarker: function(ctx, x, index) |
| { |
| throw new Error("Not implemented"); |
| }, |
| |
| visible: function() |
| { |
| return this._memorySidebarView.isShowing(); |
| }, |
| |
| show: function() |
| { |
| var anchor = (this._containerAnchor.nextSibling); |
| this._memorySidebarView.show(this._timelinePanel.element, anchor); |
| this._updateSize(); |
| this._refreshDividers(); |
| setTimeout(this._draw.bind(this), 0); |
| }, |
| |
| refresh: function() |
| { |
| this._updateSize(); |
| this._refreshDividers(); |
| this._draw(); |
| this._refreshCurrentValues(); |
| }, |
| |
| hide: function() |
| { |
| this._memorySidebarView.detach(); |
| }, |
| |
| _refreshDividers: function() |
| { |
| this._timelineGrid.updateDividers(this._timelinePanel.calculator); |
| }, |
| |
| _setVerticalClip: function(originY, height) |
| { |
| this._originY = originY; |
| this._clippedHeight = height; |
| }, |
| |
| _calculateXValues: function() |
| { |
| if (!this._counters.length) |
| return; |
| |
| var width = this._canvas.width; |
| var xFactor = width / (this._maxTime - this._minTime); |
| |
| this._counters[this._minimumIndex].x = 0; |
| for (var i = this._minimumIndex + 1; i < this._maximumIndex; i++) |
| this._counters[i].x = xFactor * (this._counters[i].time - this._minTime); |
| this._counters[this._maximumIndex].x = width; |
| }, |
| |
| _clear: function() { |
| var ctx = this._canvas.getContext("2d"); |
| ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
| this._discardImageUnderMarker(); |
| }, |
| |
| _discardImageUnderMarker: function() |
| { |
| throw new Error("Not implemented"); |
| } |
| } |
| |
| ; |
| |
| |
| |
| WebInspector.DOMCountersGraph = function(timelinePanel, model, sidebarWidth) |
| { |
| WebInspector.MemoryStatistics.call(this, timelinePanel, model, sidebarWidth); |
| } |
| |
| |
| 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 = []; |
| } |
| |
| |
| 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 = { |
| |
| 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); |
| }, |
| |
| |
| 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 |
| }; |
| }, |
| |
| |
| 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"); |
| }, |
| |
| |
| _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; |
| }, |
| |
| |
| _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]); |
| }, |
| |
| |
| _restoreImageUnderMarker: function(ctx) |
| { |
| for (var i = 0; i < this._counterUI.length; i++) { |
| var counterUI = this._counterUI[i]; |
| if (!counterUI.visible) |
| continue; |
| counterUI.restoreImageUnderMarker(ctx); |
| } |
| }, |
| |
| |
| _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); |
| } |
| }, |
| |
| |
| _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(); |
| } |
| }, |
| |
| |
| _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 |
| } |
| |
| ; |
| |
| |
| |
| WebInspector.NativeMemoryGraph = function(timelinePanel, model, sidebarWidth) |
| { |
| WebInspector.MemoryStatistics.call(this, timelinePanel, model, sidebarWidth); |
| } |
| |
| |
| WebInspector.NativeMemoryGraph.Counter = function(time, nativeCounters) |
| { |
| WebInspector.MemoryStatistics.Counter.call(this, time); |
| this.nativeCounters = nativeCounters; |
| } |
| |
| |
| WebInspector.NativeMemoryCounterUI = function(memoryCountersPane, title, hsl, valueGetter) |
| { |
| var swatchColor = this._hslToString(hsl); |
| WebInspector.CounterUIBase.call(this, memoryCountersPane, title, swatchColor, valueGetter); |
| this._value = this._swatch.element.createChild("span", "memory-category-value"); |
| |
| const borderLightnessDifference = 3; |
| hsl[2] -= borderLightnessDifference; |
| this.strokeColor = this._hslToString(hsl); |
| this.graphYValues = []; |
| } |
| |
| WebInspector.NativeMemoryCounterUI.prototype = { |
| _hslToString: function(hsl) |
| { |
| return "hsl(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%)"; |
| }, |
| |
| updateCurrentValue: function(countersEntry) |
| { |
| var bytes = this.valueGetter(countersEntry); |
| var megabytes = bytes / (1024 * 1024); |
| this._value.textContent = WebInspector.UIString("%.1f\u2009MB", megabytes); |
| }, |
| |
| clearCurrentValueAndMarker: function(ctx) |
| { |
| this._value.textContent = ""; |
| }, |
| |
| __proto__: WebInspector.CounterUIBase.prototype |
| } |
| |
| |
| WebInspector.NativeMemoryGraph.prototype = { |
| _createCurrentValuesBar: function() |
| { |
| }, |
| |
| _createCounterUIList: function() |
| { |
| var nativeCounters = [ |
| "JSExternalResources", |
| "CSS", |
| "GlyphCache", |
| "Image", |
| "Resources", |
| "DOM", |
| "Rendering", |
| "Audio", |
| "WebInspector", |
| "JSHeap.Used", |
| "JSHeap.Unused", |
| "MallocWaste", |
| "Other", |
| "PrivateBytes", |
| ]; |
| |
| |
| function getCounterValue(name, entry) |
| { |
| return (entry.nativeCounters && entry.nativeCounters[name]) || 0; |
| } |
| |
| var list = []; |
| for (var i = nativeCounters.length - 1; i >= 0; i--) { |
| var name = nativeCounters[i]; |
| if ("PrivateBytes" === name) { |
| var counterUI = new WebInspector.NativeMemoryCounterUI(this, "Total", [0, 0, 0], getCounterValue.bind(this, name)) |
| this._privateBytesCounter = counterUI; |
| } else { |
| var counterUI = new WebInspector.NativeMemoryCounterUI(this, name, [i * 20, 65, 63], getCounterValue.bind(this, name)) |
| list.push(counterUI); |
| } |
| } |
| return list.reverse(); |
| }, |
| |
| _canvasHeight: function() |
| { |
| return this._canvasContainer.offsetHeight; |
| }, |
| |
| |
| _onRecordAdded: function(event) |
| { |
| var statistics = this._counters; |
| function addStatistics(record) |
| { |
| var nativeCounters = record["nativeHeapStatistics"]; |
| if (!nativeCounters) |
| return; |
| |
| var knownSize = 0; |
| for (var name in nativeCounters) { |
| if (name === "PrivateBytes") |
| continue; |
| knownSize += nativeCounters[name]; |
| } |
| nativeCounters["Other"] = nativeCounters["PrivateBytes"] - knownSize; |
| |
| statistics.push(new WebInspector.NativeMemoryGraph.Counter( |
| record.endTime || record.startTime, |
| nativeCounters |
| )); |
| } |
| WebInspector.TimelinePresentationModel.forAllRecords([event.data], null, addStatistics); |
| }, |
| |
| _draw: function() |
| { |
| WebInspector.MemoryStatistics.prototype._draw.call(this); |
| |
| var maxValue = this._maxCounterValue(); |
| this._resetTotalValues(); |
| |
| var previousCounterUI; |
| for (var i = 0; i < this._counterUI.length; i++) { |
| this._drawGraph(this._counterUI[i], previousCounterUI, maxValue); |
| if (this._counterUI[i].visible) |
| previousCounterUI = this._counterUI[i]; |
| } |
| }, |
| |
| |
| _clearCurrentValueAndMarker: function(ctx) |
| { |
| WebInspector.MemoryStatistics.prototype._clearCurrentValueAndMarker.call(this, ctx); |
| this._privateBytesCounter.clearCurrentValueAndMarker(ctx); |
| }, |
| |
| _updateCurrentValue: function(counterEntry) |
| { |
| WebInspector.MemoryStatistics.prototype._updateCurrentValue.call(this, counterEntry); |
| this._privateBytesCounter.updateCurrentValue(counterEntry); |
| }, |
| |
| |
| _restoreImageUnderMarker: function(ctx) |
| { |
| if (this._imageUnderMarker) |
| ctx.putImageData(this._imageUnderMarker.imageData, this._imageUnderMarker.x, this._imageUnderMarker.y); |
| this._discardImageUnderMarker(); |
| }, |
| |
| |
| _saveImageUnderMarker: function(ctx, left, top, right, bottom) |
| { |
| var imageData = ctx.getImageData(left, top, right, bottom); |
| this._imageUnderMarker = { |
| x: left, |
| y: top, |
| imageData: imageData |
| }; |
| }, |
| |
| |
| _drawMarker: function(ctx, x, index) |
| { |
| var left = this._counters[index].x; |
| var right = index + 1 < this._counters.length ? this._counters[index + 1].x : left; |
| var top = this._originY; |
| top = 0; |
| var bottom = top + this._clippedHeight; |
| bottom += this._originY; |
| |
| this._saveImageUnderMarker(ctx, left, top, right, bottom); |
| |
| ctx.beginPath(); |
| ctx.moveTo(left, top); |
| ctx.lineTo(right, top); |
| ctx.lineTo(right, bottom); |
| ctx.lineTo(left, bottom); |
| ctx.lineWidth = 1; |
| ctx.closePath(); |
| ctx.fillStyle = "rgba(220,220,220,0.3)"; |
| ctx.fill(); |
| }, |
| |
| |
| _maxCounterValue: function() |
| { |
| if (!this._counters.length) |
| return 0; |
| |
| var valueGetter = this._privateBytesCounter.valueGetter; |
| var result = 0; |
| for (var i = this._minimumIndex; i < this._maximumIndex; i++) { |
| var counter = this._counters[i]; |
| var value = valueGetter(counter); |
| if (value > result) |
| result = value; |
| } |
| return result; |
| }, |
| |
| _resetTotalValues: function() |
| { |
| for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { |
| var counter = this._counters[i]; |
| counter.total = 0; |
| } |
| }, |
| |
| |
| _drawGraph: function(counterUI, previousCounterUI, maxTotalValue) |
| { |
| 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; |
| |
| if (!counterUI.visible) |
| return; |
| |
| for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { |
| var counter = this._counters[i]; |
| var value = valueGetter(counter); |
| counter.total += value; |
| } |
| |
| var yValues = counterUI.graphYValues; |
| yValues.length = this._counters.length; |
| |
| var maxYRange = maxTotalValue; |
| var yFactor = maxYRange ? height / (maxYRange) : 1; |
| |
| ctx.beginPath(); |
| if (previousCounterUI) { |
| var prevYValues = previousCounterUI.graphYValues; |
| var currentY = prevYValues[this._maximumIndex]; |
| ctx.moveTo(width, currentY); |
| var currentX = width; |
| for (var i = this._maximumIndex - 1; i >= this._minimumIndex; i--) { |
| currentY = prevYValues[i]; |
| currentX = this._counters[i].x; |
| ctx.lineTo(currentX, currentY); |
| } |
| } else { |
| var lastY = originY + height; |
| ctx.moveTo(width, lastY); |
| ctx.lineTo(0, lastY); |
| } |
| |
| var currentY = originY + (height - this._counters[this._minimumIndex].total * yFactor); |
| ctx.lineTo(0, currentY); |
| for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { |
| var counter = this._counters[i]; |
| var x = counter.x; |
| currentY = originY + (height - counter.total * yFactor); |
| ctx.lineTo(x, currentY); |
| |
| yValues[i] = currentY; |
| } |
| ctx.lineTo(width, currentY); |
| ctx.closePath(); |
| ctx.lineWidth = 1; |
| |
| ctx.strokeStyle = counterUI.strokeColor; |
| ctx.fillStyle = counterUI.graphColor; |
| ctx.fill(); |
| ctx.stroke(); |
| }, |
| |
| _discardImageUnderMarker: function() |
| { |
| delete this._imageUnderMarker; |
| }, |
| |
| __proto__: WebInspector.MemoryStatistics.prototype |
| } |
| |
| ; |
| |
| |
| |
| WebInspector.TimelineModel = function() |
| { |
| this._records = []; |
| this._stringPool = new StringPool(); |
| this._minimumRecordTime = -1; |
| this._maximumRecordTime = -1; |
| this._collectionEnabled = false; |
| |
| WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onRecordAdded, this); |
| } |
| |
| WebInspector.TimelineModel.TransferChunkLengthBytes = 5000000; |
| |
| WebInspector.TimelineModel.RecordType = { |
| Root: "Root", |
| Program: "Program", |
| EventDispatch: "EventDispatch", |
| |
| BeginFrame: "BeginFrame", |
| ScheduleStyleRecalculation: "ScheduleStyleRecalculation", |
| RecalculateStyles: "RecalculateStyles", |
| InvalidateLayout: "InvalidateLayout", |
| Layout: "Layout", |
| Paint: "Paint", |
| Rasterize: "Rasterize", |
| ScrollLayer: "ScrollLayer", |
| DecodeImage: "DecodeImage", |
| ResizeImage: "ResizeImage", |
| CompositeLayers: "CompositeLayers", |
| |
| ParseHTML: "ParseHTML", |
| |
| TimerInstall: "TimerInstall", |
| TimerRemove: "TimerRemove", |
| TimerFire: "TimerFire", |
| |
| XHRReadyStateChange: "XHRReadyStateChange", |
| XHRLoad: "XHRLoad", |
| EvaluateScript: "EvaluateScript", |
| |
| MarkLoad: "MarkLoad", |
| MarkDOMContent: "MarkDOMContent", |
| |
| TimeStamp: "TimeStamp", |
| Time: "Time", |
| TimeEnd: "TimeEnd", |
| |
| ScheduleResourceRequest: "ScheduleResourceRequest", |
| ResourceSendRequest: "ResourceSendRequest", |
| ResourceReceiveResponse: "ResourceReceiveResponse", |
| ResourceReceivedData: "ResourceReceivedData", |
| ResourceFinish: "ResourceFinish", |
| |
| FunctionCall: "FunctionCall", |
| GCEvent: "GCEvent", |
| |
| RequestAnimationFrame: "RequestAnimationFrame", |
| CancelAnimationFrame: "CancelAnimationFrame", |
| FireAnimationFrame: "FireAnimationFrame", |
| |
| WebSocketCreate : "WebSocketCreate", |
| WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest", |
| WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse", |
| WebSocketDestroy : "WebSocketDestroy", |
| } |
| |
| WebInspector.TimelineModel.Events = { |
| RecordAdded: "RecordAdded", |
| RecordsCleared: "RecordsCleared" |
| } |
| |
| WebInspector.TimelineModel.startTimeInSeconds = function(record) |
| { |
| return record.startTime / 1000; |
| } |
| |
| WebInspector.TimelineModel.endTimeInSeconds = function(record) |
| { |
| return (typeof record.endTime === "undefined" ? record.startTime : record.endTime) / 1000; |
| } |
| |
| WebInspector.TimelineModel.durationInSeconds = function(record) |
| { |
| return WebInspector.TimelineModel.endTimeInSeconds(record) - WebInspector.TimelineModel.startTimeInSeconds(record); |
| } |
| |
| |
| WebInspector.TimelineModel.aggregateTimeForRecord = function(total, rawRecord) |
| { |
| var childrenTime = 0; |
| var children = rawRecord["children"] || []; |
| for (var i = 0; i < children.length; ++i) { |
| WebInspector.TimelineModel.aggregateTimeForRecord(total, children[i]); |
| childrenTime += WebInspector.TimelineModel.durationInSeconds(children[i]); |
| } |
| var categoryName = WebInspector.TimelinePresentationModel.recordStyle(rawRecord).category.name; |
| var ownTime = WebInspector.TimelineModel.durationInSeconds(rawRecord) - childrenTime; |
| total[categoryName] = (total[categoryName] || 0) + ownTime; |
| } |
| |
| |
| WebInspector.TimelineModel.aggregateTimeByCategory = function(total, addend) |
| { |
| for (var category in addend) |
| total[category] = (total[category] || 0) + addend[category]; |
| } |
| |
| WebInspector.TimelineModel.prototype = { |
| |
| startRecord: function(includeDomCounters, includeNativeMemoryStatistics) |
| { |
| if (this._collectionEnabled) |
| return; |
| this.reset(); |
| var maxStackFrames = WebInspector.settings.timelineLimitStackFramesFlag.get() ? WebInspector.settings.timelineStackFramesToCapture.get() : 30; |
| WebInspector.timelineManager.start(maxStackFrames, includeDomCounters, includeNativeMemoryStatistics); |
| this._collectionEnabled = true; |
| }, |
| |
| stopRecord: function() |
| { |
| if (!this._collectionEnabled) |
| return; |
| WebInspector.timelineManager.stop(); |
| this._collectionEnabled = false; |
| }, |
| |
| get records() |
| { |
| return this._records; |
| }, |
| |
| _onRecordAdded: function(event) |
| { |
| if (this._collectionEnabled) |
| this._addRecord(event.data); |
| }, |
| |
| _addRecord: function(record) |
| { |
| this._stringPool.internObjectStrings(record); |
| this._records.push(record); |
| this._updateBoundaries(record); |
| this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record); |
| }, |
| |
| |
| loadFromFile: function(file, progress) |
| { |
| var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress); |
| var fileReader = this._createFileReader(file, delegate); |
| var loader = new WebInspector.TimelineModelLoader(this, fileReader, progress); |
| fileReader.start(loader); |
| }, |
| |
| |
| loadFromURL: function(url, progress) |
| { |
| var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress); |
| var urlReader = new WebInspector.ChunkedXHRReader(url, delegate); |
| var loader = new WebInspector.TimelineModelLoader(this, urlReader, progress); |
| urlReader.start(loader); |
| }, |
| |
| _createFileReader: function(file, delegate) |
| { |
| return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModel.TransferChunkLengthBytes, delegate); |
| }, |
| |
| _createFileWriter: function(fileName, callback) |
| { |
| var stream = new WebInspector.FileOutputStream(); |
| stream.open(fileName, callback); |
| }, |
| |
| saveToFile: function() |
| { |
| var now = new Date(); |
| var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json"; |
| function callback(stream) |
| { |
| var saver = new WebInspector.TimelineSaver(stream); |
| saver.save(this._records, window.navigator.appVersion); |
| } |
| this._createFileWriter(fileName, callback.bind(this)); |
| }, |
| |
| reset: function() |
| { |
| this._records = []; |
| this._stringPool.reset(); |
| this._minimumRecordTime = -1; |
| this._maximumRecordTime = -1; |
| this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared); |
| }, |
| |
| minimumRecordTime: function() |
| { |
| return this._minimumRecordTime; |
| }, |
| |
| maximumRecordTime: function() |
| { |
| return this._maximumRecordTime; |
| }, |
| |
| _updateBoundaries: function(record) |
| { |
| var startTime = WebInspector.TimelineModel.startTimeInSeconds(record); |
| var endTime = WebInspector.TimelineModel.endTimeInSeconds(record); |
| |
| if (this._minimumRecordTime === -1 || startTime < this._minimumRecordTime) |
| this._minimumRecordTime = startTime; |
| if (this._maximumRecordTime === -1 || endTime > this._maximumRecordTime) |
| this._maximumRecordTime = endTime; |
| }, |
| |
| |
| recordOffsetInSeconds: function(rawRecord) |
| { |
| return WebInspector.TimelineModel.startTimeInSeconds(rawRecord) - this._minimumRecordTime; |
| }, |
| |
| __proto__: WebInspector.Object.prototype |
| } |
| |
| |
| WebInspector.TimelineModelLoader = function(model, reader, progress) |
| { |
| this._model = model; |
| this._reader = reader; |
| this._progress = progress; |
| this._buffer = ""; |
| this._firstChunk = true; |
| } |
| |
| WebInspector.TimelineModelLoader.prototype = { |
| |
| write: function(chunk) |
| { |
| var data = this._buffer + chunk; |
| var lastIndex = 0; |
| var index; |
| do { |
| index = lastIndex; |
| lastIndex = WebInspector.findBalancedCurlyBrackets(data, index); |
| } while (lastIndex !== -1) |
| |
| var json = data.slice(0, index) + "]"; |
| this._buffer = data.slice(index); |
| |
| if (!index) |
| return; |
| |
| |
| if (!this._firstChunk) |
| json = "[0" + json; |
| |
| var items; |
| try { |
| items = (JSON.parse(json)); |
| } catch (e) { |
| WebInspector.showErrorMessage("Malformed timeline data."); |
| this._model.reset(); |
| this._reader.cancel(); |
| this._progress.done(); |
| return; |
| } |
| |
| if (this._firstChunk) { |
| this._version = items[0]; |
| this._firstChunk = false; |
| this._model.reset(); |
| } |
| |
| |
| for (var i = 1, size = items.length; i < size; ++i) |
| this._model._addRecord(items[i]); |
| }, |
| |
| close: function() { } |
| } |
| |
| |
| WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress) |
| { |
| this._model = model; |
| this._progress = progress; |
| } |
| |
| WebInspector.TimelineModelLoadFromFileDelegate.prototype = { |
| onTransferStarted: function() |
| { |
| this._progress.setTitle(WebInspector.UIString("Loading\u2026")); |
| }, |
| |
| |
| onChunkTransferred: function(reader) |
| { |
| if (this._progress.isCanceled()) { |
| reader.cancel(); |
| this._progress.done(); |
| this._model.reset(); |
| return; |
| } |
| |
| var totalSize = reader.fileSize(); |
| if (totalSize) { |
| this._progress.setTotalWork(totalSize); |
| this._progress.setWorked(reader.loadedSize()); |
| } |
| }, |
| |
| onTransferFinished: function() |
| { |
| this._progress.done(); |
| }, |
| |
| |
| onError: function(reader, event) |
| { |
| this._progress.done(); |
| this._model.reset(); |
| switch (event.target.error.code) { |
| case FileError.NOT_FOUND_ERR: |
| WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" not found.", reader.fileName())); |
| break; |
| case FileError.NOT_READABLE_ERR: |
| WebInspector.showErrorMessage(WebInspector.UIString("File \"%s\" is not readable", reader.fileName())); |
| break; |
| case FileError.ABORT_ERR: |
| break; |
| default: |
| WebInspector.showErrorMessage(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName())); |
| } |
| } |
| } |
| |
| |
| WebInspector.TimelineSaver = function(stream) |
| { |
| this._stream = stream; |
| } |
| |
| WebInspector.TimelineSaver.prototype = { |
| |
| save: function(records, version) |
| { |
| this._records = records; |
| this._recordIndex = 0; |
| this._prologue = "[" + JSON.stringify(version); |
| |
| this._writeNextChunk(this._stream); |
| }, |
| |
| _writeNextChunk: function(stream) |
| { |
| const separator = ",\n"; |
| var data = []; |
| var length = 0; |
| |
| if (this._prologue) { |
| data.push(this._prologue); |
| length += this._prologue.length; |
| delete this._prologue; |
| } else { |
| if (this._recordIndex === this._records.length) { |
| stream.close(); |
| return; |
| } |
| data.push(""); |
| } |
| while (this._recordIndex < this._records.length) { |
| var item = JSON.stringify(this._records[this._recordIndex]); |
| var itemLength = item.length + separator.length; |
| if (length + itemLength > WebInspector.TimelineModel.TransferChunkLengthBytes) |
| break; |
| length += itemLength; |
| data.push(item); |
| ++this._recordIndex; |
| } |
| if (this._recordIndex === this._records.length) |
| data.push(data.pop() + "]"); |
| stream.write(data.join(separator), this._writeNextChunk.bind(this)); |
| } |
| } |
| ; |
| |
| |
| |
| WebInspector.TimelineOverviewPane = function(model) |
| { |
| WebInspector.View.call(this); |
| this.element.id = "timeline-overview-panel"; |
| |
| this._windowStartTime = 0; |
| this._windowEndTime = Infinity; |
| this._eventDividers = []; |
| |
| this._model = model; |
| |
| this._topPaneSidebarElement = document.createElement("div"); |
| this._topPaneSidebarElement.id = "timeline-overview-sidebar"; |
| |
| var overviewTreeElement = document.createElement("ol"); |
| overviewTreeElement.className = "sidebar-tree"; |
| this._topPaneSidebarElement.appendChild(overviewTreeElement); |
| this.element.appendChild(this._topPaneSidebarElement); |
| |
| var topPaneSidebarTree = new TreeOutline(overviewTreeElement); |
| |
| this._overviewItems = {}; |
| this._overviewItems[WebInspector.TimelineOverviewPane.Mode.Events] = new WebInspector.SidebarTreeElement("timeline-overview-sidebar-events", |
| WebInspector.UIString("Events")); |
| this._overviewItems[WebInspector.TimelineOverviewPane.Mode.Frames] = new WebInspector.SidebarTreeElement("timeline-overview-sidebar-frames", |
| WebInspector.UIString("Frames")); |
| this._overviewItems[WebInspector.TimelineOverviewPane.Mode.Memory] = new WebInspector.SidebarTreeElement("timeline-overview-sidebar-memory", |
| WebInspector.UIString("Memory")); |
| |
| for (var mode in this._overviewItems) { |
| var item = this._overviewItems[mode]; |
| item.onselect = this.setMode.bind(this, mode); |
| topPaneSidebarTree.appendChild(item); |
| } |
| |
| this._overviewGrid = new WebInspector.OverviewGrid("timeline"); |
| this.element.appendChild(this._overviewGrid.element); |
| |
| var separatorElement = document.createElement("div"); |
| separatorElement.id = "timeline-overview-separator"; |
| this.element.appendChild(separatorElement); |
| |
| this._innerSetMode(WebInspector.TimelineOverviewPane.Mode.Events); |
| |
| var categories = WebInspector.TimelinePresentationModel.categories(); |
| for (var category in categories) |
| categories[category].addEventListener(WebInspector.TimelineCategory.Events.VisibilityChanged, this._onCategoryVisibilityChanged, this); |
| |
| this._overviewCalculator = new WebInspector.TimelineOverviewCalculator(); |
| |
| model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this); |
| model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._reset, this); |
| this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); |
| } |
| |
| WebInspector.TimelineOverviewPane.Mode = { |
| Events: "Events", |
| Frames: "Frames", |
| Memory: "Memory" |
| }; |
| |
| WebInspector.TimelineOverviewPane.Events = { |
| ModeChanged: "ModeChanged", |
| WindowChanged: "WindowChanged" |
| }; |
| |
| WebInspector.TimelineOverviewPane.prototype = { |
| wasShown: function() |
| { |
| this._update(); |
| }, |
| |
| onResize: function() |
| { |
| this._update(); |
| }, |
| |
| setMode: function(newMode) |
| { |
| if (this._currentMode === newMode) |
| return; |
| this._innerSetMode(newMode); |
| this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.ModeChanged, this._currentMode); |
| this._update(); |
| }, |
| |
| _innerSetMode: function(newMode) |
| { |
| if (this._overviewControl) |
| this._overviewControl.detach(); |
| |
| this._currentMode = newMode; |
| this._overviewControl = this._createOverviewControl(); |
| this._overviewControl.show(this._overviewGrid.element); |
| this._overviewItems[this._currentMode].revealAndSelect(false); |
| }, |
| |
| |
| _createOverviewControl: function() |
| { |
| switch (this._currentMode) { |
| case WebInspector.TimelineOverviewPane.Mode.Events: |
| return new WebInspector.TimelineEventOverview(this._model); |
| case WebInspector.TimelineOverviewPane.Mode.Frames: |
| return new WebInspector.TimelineFrameOverview(this._model); |
| case WebInspector.TimelineOverviewPane.Mode.Memory: |
| return new WebInspector.TimelineMemoryOverview(this._model); |
| } |
| throw new Error("Invalid overview mode: " + this._currentMode); |
| }, |
| |
| _onCategoryVisibilityChanged: function(event) |
| { |
| this._overviewControl.categoryVisibilityChanged(); |
| }, |
| |
| _update: function() |
| { |
| delete this._refreshTimeout; |
| |
| this._updateWindow(); |
| this._overviewCalculator.setWindow(this._model.minimumRecordTime(), this._model.maximumRecordTime()); |
| this._overviewCalculator.setDisplayWindow(0, this._overviewGrid.clientWidth()); |
| |
| this._overviewControl.update(); |
| this._overviewGrid.updateDividers(this._overviewCalculator); |
| this._updateEventDividers(); |
| }, |
| |
| _updateEventDividers: function() |
| { |
| var records = this._eventDividers; |
| this._overviewGrid.removeEventDividers(); |
| var dividers = []; |
| for (var i = 0; i < records.length; ++i) { |
| var record = records[i]; |
| var positions = this._overviewCalculator.computeBarGraphPercentages(record); |
| var dividerPosition = Math.round(positions.start * 10); |
| if (dividers[dividerPosition]) |
| continue; |
| var divider = WebInspector.TimelinePresentationModel.createEventDivider(record.type); |
| divider.style.left = positions.start + "%"; |
| dividers[dividerPosition] = divider; |
| } |
| this._overviewGrid.addEventDividers(dividers); |
| }, |
| |
| |
| sidebarResized: function(width) |
| { |
| this._overviewGrid.element.style.left = width + "px"; |
| this._topPaneSidebarElement.style.width = width + "px"; |
| this._update(); |
| }, |
| |
| |
| addFrame: function(frame) |
| { |
| this._overviewControl.addFrame(frame); |
| this._scheduleRefresh(); |
| }, |
| |
| |
| zoomToFrame: function(frame) |
| { |
| var frameOverview = (this._overviewControl); |
| var window = frameOverview.framePosition(frame); |
| if (!window) |
| return; |
| |
| this._overviewGrid.setWindowPosition(window.start, window.end); |
| }, |
| |
| _onRecordAdded: function(event) |
| { |
| var record = event.data; |
| var eventDividers = this._eventDividers; |
| function addEventDividers(record) |
| { |
| if (WebInspector.TimelinePresentationModel.isEventDivider(record)) |
| eventDividers.push(record); |
| } |
| WebInspector.TimelinePresentationModel.forAllRecords([record], addEventDividers); |
| this._scheduleRefresh(); |
| }, |
| |
| _reset: function() |
| { |
| this._windowStartTime = 0; |
| this._windowEndTime = Infinity; |
| this._overviewCalculator.reset(); |
| this._overviewGrid.reset(); |
| this._eventDividers = []; |
| this._overviewGrid.updateDividers(this._overviewCalculator); |
| this._overviewControl.reset(); |
| this._update(); |
| }, |
| |
| windowStartTime: function() |
| { |
| return this._windowStartTime || this._model.minimumRecordTime(); |
| }, |
| |
| windowEndTime: function() |
| { |
| return this._windowEndTime < Infinity ? this._windowEndTime : this._model.maximumRecordTime(); |
| }, |
| |
| windowLeft: function() |
| { |
| return this._overviewGrid.windowLeft(); |
| }, |
| |
| windowRight: function() |
| { |
| return this._overviewGrid.windowRight(); |
| }, |
| |
| _onWindowChanged: function() |
| { |
| if (this._ignoreWindowChangedEvent) |
| return; |
| var times = this._overviewControl.windowTimes(this.windowLeft(), this.windowRight()); |
| this._windowStartTime = times.startTime; |
| this._windowEndTime = times.endTime; |
| this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged); |
| }, |
| |
| |
| setWindowTimes: function(left, right) |
| { |
| this._windowStartTime = left; |
| this._windowEndTime = right; |
| this._updateWindow(); |
| }, |
| |
| _updateWindow: function() |
| { |
| var offset = this._model.minimumRecordTime(); |
| var timeSpan = this._model.maximumRecordTime() - offset; |
| var left = this._windowStartTime ? (this._windowStartTime - offset) / timeSpan : 0; |
| var right = this._windowEndTime < Infinity ? (this._windowEndTime - offset) / timeSpan : 1; |
| this._ignoreWindowChangedEvent = true; |
| this._overviewGrid.setWindow(left, right); |
| this._ignoreWindowChangedEvent = false; |
| }, |
| |
| _scheduleRefresh: function() |
| { |
| if (this._refreshTimeout) |
| return; |
| if (!this.isShowing()) |
| return; |
| this._refreshTimeout = setTimeout(this._update.bind(this), 300); |
| }, |
| |
| __proto__: WebInspector.View.prototype |
| } |
| |
| |
| WebInspector.TimelineOverviewCalculator = function() |
| { |
| } |
| |
| WebInspector.TimelineOverviewCalculator.prototype = { |
| |
| computePosition: function(time) |
| { |
| return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this.paddingLeft; |
| }, |
| |
| computeBarGraphPercentages: function(record) |
| { |
| var start = (WebInspector.TimelineModel.startTimeInSeconds(record) - this._minimumBoundary) / this.boundarySpan() * 100; |
| var end = (WebInspector.TimelineModel.endTimeInSeconds(record) - this._minimumBoundary) / this.boundarySpan() * 100; |
| return {start: start, end: end}; |
| }, |
| |
| |
| setWindow: function(minimum, maximum) |
| { |
| this._minimumBoundary = minimum >= 0 ? minimum : undefined; |
| this._maximumBoundary = maximum >= 0 ? maximum : undefined; |
| }, |
| |
| |
| setDisplayWindow: function(paddingLeft, clientWidth) |
| { |
| this._workingArea = clientWidth - paddingLeft; |
| this.paddingLeft = paddingLeft; |
| }, |
| |
| reset: function() |
| { |
| this.setWindow(); |
| }, |
| |
| formatTime: function(value) |
| { |
| return Number.secondsToString(value); |
| }, |
| |
| maximumBoundary: function() |
| { |
| return this._maximumBoundary; |
| }, |
| |
| minimumBoundary: function() |
| { |
| return this._minimumBoundary; |
| }, |
| |
| zeroTime: function() |
| { |
| return this._minimumBoundary; |
| }, |
| |
| boundarySpan: function() |
| { |
| return this._maximumBoundary - this._minimumBoundary; |
| } |
| } |
| |
| |
| WebInspector.TimelineOverviewBase = function(model) |
| { |
| WebInspector.View.call(this); |
| this._model = model; |
| this._canvas = this.element.createChild("canvas", "fill"); |
| } |
| |
| WebInspector.TimelineOverviewBase.prototype = { |
| update: function() { }, |
| reset: function() { }, |
| |
| categoryVisibilityChanged: function() { }, |
| |
| |
| addFrame: function(frame) { }, |
| |
| |
| windowTimes: function(windowLeft, windowRight) |
| { |
| var absoluteMin = this._model.minimumRecordTime(); |
| var absoluteMax = this._model.maximumRecordTime(); |
| return { |
| startTime: absoluteMin + (absoluteMax - absoluteMin) * windowLeft, |
| endTime: absoluteMin + (absoluteMax - absoluteMin) * windowRight |
| }; |
| }, |
| |
| __proto__: WebInspector.View.prototype |
| } |
| |
| |
| WebInspector.TimelineMemoryOverview = function(model) |
| { |
| WebInspector.TimelineOverviewBase.call(this, model); |
| this.element.id = "timeline-overview-memory"; |
| this.element.classList.add("fill"); |
| |
| this._maxHeapSizeLabel = this.element.createChild("div", "max memory-graph-label"); |
| this._minHeapSizeLabel = this.element.createChild("div", "min memory-graph-label"); |
| } |
| |
| WebInspector.TimelineMemoryOverview.prototype = { |
| update: function() |
| { |
| var records = this._model.records; |
| if (!records.length) |
| return; |
| |
| const yPadding = 5; |
| this._canvas.width = this.element.clientWidth; |
| this._canvas.height = this.element.clientHeight - yPadding; |
| |
| const lowerOffset = 3; |
| var maxUsedHeapSize = 0; |
| var minUsedHeapSize = 100000000000; |
| var minTime = this._model.minimumRecordTime(); |
| var maxTime = this._model.maximumRecordTime();; |
| WebInspector.TimelinePresentationModel.forAllRecords(records, function(r) { |
| maxUsedHeapSize = Math.max(maxUsedHeapSize, r.usedHeapSize || maxUsedHeapSize); |
| minUsedHeapSize = Math.min(minUsedHeapSize, r.usedHeapSize || minUsedHeapSize); |
| }); |
| minUsedHeapSize = Math.min(minUsedHeapSize, maxUsedHeapSize); |
| |
| var width = this._canvas.width; |
| var height = this._canvas.height - lowerOffset; |
| var xFactor = width / (maxTime - minTime); |
| var yFactor = height / (maxUsedHeapSize - minUsedHeapSize); |
| |
| var histogram = new Array(width); |
| WebInspector.TimelinePresentationModel.forAllRecords(records, function(r) { |
| if (!r.usedHeapSize) |
| return; |
| var x = Math.round((WebInspector.TimelineModel.endTimeInSeconds(r) - minTime) * xFactor); |
| var y = Math.round((r.usedHeapSize - minUsedHeapSize) * yFactor); |
| histogram[x] = Math.max(histogram[x] || 0, y); |
| }); |
| |
| var ctx = this._canvas.getContext("2d"); |
| this._clear(ctx); |
| |
| |
| height = height + 1; |
| |
| ctx.beginPath(); |
| var initialY = 0; |
| for (var k = 0; k < histogram.length; k++) { |
| if (histogram[k]) { |
| initialY = histogram[k]; |
| break; |
| } |
| } |
| ctx.moveTo(0, height - initialY); |
| |
| for (var x = 0; x < histogram.length; x++) { |
| if (!histogram[x]) |
| continue; |
| ctx.lineTo(x, height - histogram[x]); |
| } |
| |
| ctx.lineWidth = 0.5; |
| ctx.strokeStyle = "rgba(20,0,0,0.8)"; |
| ctx.stroke(); |
| |
| ctx.fillStyle = "rgba(214,225,254, 0.8);"; |
| ctx.lineTo(width, 60); |
| ctx.lineTo(0, 60); |
| ctx.lineTo(0, height - initialY); |
| ctx.fill(); |
| ctx.closePath(); |
| |
| this._maxHeapSizeLabel.textContent = Number.bytesToString(maxUsedHeapSize); |
| this._minHeapSizeLabel.textContent = Number.bytesToString(minUsedHeapSize); |
| }, |
| |
| _clear: function(ctx) |
| { |
| ctx.fillStyle = "rgba(255,255,255,0.8)"; |
| ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); |
| }, |
| |
| __proto__: WebInspector.TimelineOverviewBase.prototype |
| } |
| |
| |
| WebInspector.TimelineEventOverview = function(model) |
| { |
| WebInspector.TimelineOverviewBase.call(this, model); |
| |
| this.element.id = "timeline-overview-events"; |
| this._context = this._canvas.getContext("2d"); |
| |
| this._fillStyles = {}; |
| var categories = WebInspector.TimelinePresentationModel.categories(); |
| for (var category in categories) |
| this._fillStyles[category] = WebInspector.TimelinePresentationModel.createFillStyleForCategory(this._context, 0, WebInspector.TimelineEventOverview._innerStripHeight, categories[category]); |
| |
| this._disabledCategoryFillStyle = WebInspector.TimelinePresentationModel.createFillStyle(this._context, 0, WebInspector.TimelineEventOverview._innerStripHeight, |
| "rgb(218, 218, 218)", "rgb(170, 170, 170)", "rgb(143, 143, 143)"); |
| |
| this._disabledCategoryBorderStyle = "rgb(143, 143, 143)"; |
| } |
| |
| |
| WebInspector.TimelineEventOverview._canvasHeight = 60; |
| |
| WebInspector.TimelineEventOverview._numberOfStrips = 3; |
| |
| WebInspector.TimelineEventOverview._stripHeight = Math.round(WebInspector.TimelineEventOverview._canvasHeight / WebInspector.TimelineEventOverview._numberOfStrips); |
| |
| WebInspector.TimelineEventOverview._stripPadding = 4; |
| |
| WebInspector.TimelineEventOverview._innerStripHeight = WebInspector.TimelineEventOverview._stripHeight - 2 * WebInspector.TimelineEventOverview._stripPadding; |
| |
| WebInspector.TimelineEventOverview.prototype = { |
| update: function() |
| { |
| |
| this._canvas.width = this.element.parentElement.clientWidth; |
| this._canvas.height = WebInspector.TimelineEventOverview._canvasHeight; |
| |
| var timeOffset = this._model.minimumRecordTime(); |
| var timeSpan = this._model.maximumRecordTime() - timeOffset; |
| var scale = this._canvas.width / timeSpan; |
| |
| var lastBarByGroup = []; |
| |
| this._context.fillStyle = "rgba(0, 0, 0, 0.05)"; |
| for (var i = 1; i < WebInspector.TimelineEventOverview._numberOfStrips; i += 2) |
| this._context.fillRect(0.5, i * WebInspector.TimelineEventOverview._stripHeight + 0.5, this._canvas.width, WebInspector.TimelineEventOverview._stripHeight); |
| |
| function appendRecord(record) |
| { |
| if (record.type === WebInspector.TimelineModel.RecordType.BeginFrame) |
| return; |
| var recordStart = Math.floor((WebInspector.TimelineModel.startTimeInSeconds(record) - timeOffset) * scale); |
| var recordEnd = Math.ceil((WebInspector.TimelineModel.endTimeInSeconds(record) - timeOffset) * scale); |
| var category = WebInspector.TimelinePresentationModel.categoryForRecord(record); |
| if (category.overviewStripGroupIndex < 0) |
| return; |
| var bar = lastBarByGroup[category.overviewStripGroupIndex]; |
| |
| const barsMergeThreshold = 2; |
| if (bar && bar.category === category && bar.end + barsMergeThreshold >= recordStart) { |
| if (recordEnd > bar.end) |
| bar.end = recordEnd; |
| return; |
| } |
| if (bar) |
| this._renderBar(bar.start, bar.end, bar.category); |
| lastBarByGroup[category.overviewStripGroupIndex] = { start: recordStart, end: recordEnd, category: category }; |
| } |
| WebInspector.TimelinePresentationModel.forAllRecords(this._model.records, appendRecord.bind(this)); |
| for (var i = 0; i < lastBarByGroup.length; ++i) { |
| if (lastBarByGroup[i]) |
| this._renderBar(lastBarByGroup[i].start, lastBarByGroup[i].end, lastBarByGroup[i].category); |
| } |
| }, |
| |
| categoryVisibilityChanged: function() |
| { |
| this.update(); |
| }, |
| |
| _renderBar: function(begin, end, category) |
| { |
| var x = begin + 0.5; |
| var y = category.overviewStripGroupIndex * WebInspector.TimelineEventOverview._stripHeight + WebInspector.TimelineEventOverview._stripPadding + 0.5; |
| var width = Math.max(end - begin, 1); |
| |
| this._context.save(); |
| this._context.translate(x, y); |
| this._context.fillStyle = category.hidden ? this._disabledCategoryFillStyle : this._fillStyles[category.name]; |
| this._context.fillRect(0, 0, width, WebInspector.TimelineEventOverview._innerStripHeight); |
| this._context.strokeStyle = category.hidden ? this._disabledCategoryBorderStyle : category.borderColor; |
| this._context.strokeRect(0, 0, width, WebInspector.TimelineEventOverview._innerStripHeight); |
| this._context.restore(); |
| }, |
| |
| __proto__: WebInspector.TimelineOverviewBase.prototype |
| } |
| |
| |
| WebInspector.TimelineFrameOverview = function(model) |
| { |
| WebInspector.TimelineOverviewBase.call(this, model); |
| this._canvas.classList.add("timeline-frame-overview-bars"); |
| this.reset(); |
| |
| this._outerPadding = 4; |
| this._maxInnerBarWidth = 10; |
| |
| |
| this._actualPadding = 5; |
| this._actualOuterBarWidth = this._maxInnerBarWidth + this._actualPadding; |
| |
| this._context = this._canvas.getContext("2d"); |
| |
| this._fillStyles = {}; |
| var categories = WebInspector.TimelinePresentationModel.categories(); |
| for (var category in categories) |
| this._fillStyles[category] = WebInspector.TimelinePresentationModel.createFillStyleForCategory(this._context, this._maxInnerBarWidth, 0, categories[category]); |
| } |
| |
| WebInspector.TimelineFrameOverview.prototype = { |
| reset: function() |
| { |
| this._recordsPerBar = 1; |
| this._barTimes = []; |
| this._frames = []; |
| }, |
| |
| update: function() |
| { |
| const minBarWidth = 4; |
| this._framesPerBar = Math.max(1, this._frames.length * minBarWidth / this.element.clientWidth); |
| this._barTimes = []; |
| var visibleFrames = this._aggregateFrames(this._framesPerBar); |
| |
| const paddingTop = 4; |
| |
| |
| |
| const targetFPS = 30; |
| var fullBarLength = 1.0 / targetFPS; |
| if (fullBarLength < this._medianFrameLength) |
| fullBarLength = Math.min(this._medianFrameLength * 2, this._maxFrameLength); |
| |
| var scale = (this._canvas.clientHeight - paddingTop) / fullBarLength; |
| this._renderBars(visibleFrames, scale); |
| }, |
| |
| |
| addFrame: function(frame) |
| { |
| this._frames.push(frame); |
| }, |
| |
| framePosition: function(frame) |
| { |
| var frameNumber = this._frames.indexOf(frame); |
| if (frameNumber < 0) |
| return; |
| var barNumber = Math.floor(frameNumber / this._framesPerBar); |
| var firstBar = this._framesPerBar > 1 ? barNumber : Math.max(barNumber - 1, 0); |
| var lastBar = this._framesPerBar > 1 ? barNumber : Math.min(barNumber + 1, this._barTimes.length - 1); |
| return { |
| start: Math.ceil(this._barNumberToScreenPosition(firstBar) - this._actualPadding / 2), |
| end: Math.floor(this._barNumberToScreenPosition(lastBar + 1) - this._actualPadding / 2) |
| } |
| }, |
| |
| |
| _aggregateFrames: function(framesPerBar) |
| { |
| var visibleFrames = []; |
| var durations = []; |
| |
| this._maxFrameLength = 0; |
| |
| for (var barNumber = 0, currentFrame = 0; currentFrame < this._frames.length; ++barNumber) { |
| var barStartTime = this._frames[currentFrame].startTime; |
| var longestFrame = null; |
| |
| for (var lastFrame = Math.min(Math.floor((barNumber + 1) * framesPerBar), this._frames.length); |
| currentFrame < lastFrame; ++currentFrame) { |
| if (!longestFrame || longestFrame.duration < this._frames[currentFrame].duration) |
| longestFrame = this._frames[currentFrame]; |
| } |
| var barEndTime = this._frames[currentFrame - 1].endTime; |
| if (longestFrame) { |
| this._maxFrameLength = Math.max(this._maxFrameLength, longestFrame.duration); |
| visibleFrames.push(longestFrame); |
| this._barTimes.push({ startTime: barStartTime, endTime: barEndTime }); |
| durations.push(longestFrame.duration); |
| } |
| } |
| this._medianFrameLength = durations.qselect(Math.floor(durations.length / 2)); |
| return visibleFrames; |
| }, |
| |
| |
| _renderBars: function(frames, scale) |
| { |
| |
| this._canvas.width = this._canvas.clientWidth; |
| this._canvas.height = this._canvas.clientHeight; |
| |
| const maxPadding = 5; |
| this._actualOuterBarWidth = Math.min((this._canvas.width - 2 * this._outerPadding) / frames.length, this._maxInnerBarWidth + maxPadding); |
| this._actualPadding = Math.min(Math.floor(this._actualOuterBarWidth / 3), maxPadding); |
| |
| var barWidth = this._actualOuterBarWidth - this._actualPadding; |
| for (var i = 0; i < frames.length; ++i) |
| this._renderBar(this._barNumberToScreenPosition(i), barWidth, frames[i], scale); |
| |
| this._drawFPSMarks(scale); |
| }, |
| |
| |
| _barNumberToScreenPosition: function(n) |
| { |
| return this._outerPadding + this._actualOuterBarWidth * n; |
| }, |
| |
| |
| _drawFPSMarks: function(scale) |
| { |
| const fpsMarks = [30, 60]; |
| |
| this._context.save(); |
| this._context.beginPath(); |
| this._context.font = "9px monospace"; |
| this._context.textAlign = "right"; |
| this._context.textBaseline = "top"; |
| |
| const labelPadding = 2; |
| var lineHeight = 12; |
| var labelTopMargin = 0; |
| |
| for (var i = 0; i < fpsMarks.length; ++i) { |
| var fps = fpsMarks[i]; |
| |
| var y = this._canvas.height - Math.floor(1.0 / fps * scale) - 0.5; |
| var label = fps + " FPS "; |
| var labelWidth = this._context.measureText(label).width; |
| var labelX = this._canvas.width; |
| var labelY; |
| |
| if (labelTopMargin < y - lineHeight) |
| labelY = y - lineHeight; |
| else if (y + lineHeight < this._canvas.height) |
| labelY = y; |
| else |
| break; |
| |
| this._context.moveTo(0, y); |
| this._context.lineTo(this._canvas.width, y); |
| |
| this._context.fillStyle = "rgba(255, 255, 255, 0.75)"; |
| this._context.fillRect(labelX - labelWidth - labelPadding, labelY, labelWidth + 2 * labelPadding, lineHeight); |
| this._context.fillStyle = "rgb(0, 0, 0)"; |
| this._context.fillText(label, labelX, labelY); |
| labelTopMargin = labelY + lineHeight; |
| } |
| this._context.strokeStyle = "rgb(51, 51, 51)"; |
| this._context.stroke(); |
| this._context.restore(); |
| }, |
| |
| _renderBar: function(left, width, frame, scale) |
| { |
| var categories = Object.keys(WebInspector.TimelinePresentationModel.categories()); |
| if (!categories.length) |
| return; |
| var x = Math.floor(left) + 0.5; |
| width = Math.floor(width); |
| |
| for (var i = 0, bottomOffset = this._canvas.height; i < categories.length; ++i) { |
| var category = categories[i]; |
| var duration = frame.timeByCategory[category]; |
| |
| if (!duration) |
| continue; |
| var height = duration * scale; |
| var y = Math.floor(bottomOffset - height) + 0.5; |
| |
| this._context.save(); |
| this._context.translate(x, 0); |
| this._context.scale(width / this._maxInnerBarWidth, 1); |
| this._context.fillStyle = this._fillStyles[category]; |
| this._context.fillRect(0, y, this._maxInnerBarWidth, Math.floor(height)); |
| this._context.strokeStyle = WebInspector.TimelinePresentationModel.categories()[category].borderColor; |
| this._context.beginPath(); |
| this._context.moveTo(0, y); |
| this._context.lineTo(this._maxInnerBarWidth, y); |
| this._context.stroke(); |
| this._context.restore(); |
| |
| bottomOffset -= height - 1; |
| } |
| |
| var y0 = Math.floor(this._canvas.height - frame.duration * scale) + 0.5; |
| var y1 = this._canvas.height + 0.5; |
| |
| this._context.strokeStyle = "rgb(90, 90, 90)"; |
| this._context.beginPath(); |
| this._context.moveTo(x, y1); |
| this._context.lineTo(x, y0); |
| this._context.lineTo(x + width, y0); |
| this._context.lineTo(x + width, y1); |
| this._context.stroke(); |
| }, |
| |
| windowTimes: function(windowLeft, windowRight) |
| { |
| var windowSpan = this.element.clientWidth; |
| var leftOffset = windowLeft * windowSpan - this._outerPadding + this._actualPadding; |
| var rightOffset = windowRight * windowSpan - this._outerPadding; |
| var bars = this.element.children; |
| var firstBar = Math.floor(Math.max(leftOffset, 0) / this._actualOuterBarWidth); |
| var lastBar = Math.min(Math.floor(rightOffset / this._actualOuterBarWidth), this._barTimes.length - 1); |
| const snapToRightTolerancePixels = 3; |
| return { |
| startTime: firstBar >= this._barTimes.length ? Infinity : this._barTimes[firstBar].startTime, |
| endTime: rightOffset + snapToRightTolerancePixels > windowSpan ? Infinity : this._barTimes[lastBar].endTime |
| } |
| }, |
| |
| __proto__: WebInspector.TimelineOverviewBase.prototype |
| } |
| |
| |
| WebInspector.TimelineWindowFilter = function(pane) |
| { |
| this._pane = pane; |
| } |
| |
| WebInspector.TimelineWindowFilter.prototype = { |
| |
| accept: function(record) |
| { |
| return record.lastChildEndTime >= this._pane._windowStartTime && record.startTime <= this._pane._windowEndTime; |
| } |
| } |
| ; |
| |
| |
| |
| WebInspector.TimelinePresentationModel = function() |
| { |
| this._linkifier = new WebInspector.Linkifier(); |
| this._glueRecords = false; |
| this._filters = []; |
| this.reset(); |
| } |
| |
| WebInspector.TimelinePresentationModel.categories = function() |
| { |
| if (WebInspector.TimelinePresentationModel._categories) |
| return WebInspector.TimelinePresentationModel._categories; |
| WebInspector.TimelinePresentationModel._categories = { |
| loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), 0, "#5A8BCC", "#8EB6E9", "#70A2E3"), |
| scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), 1, "#D8AA34", "#F3D07A", "#F1C453"), |
| rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), 2, "#8266CC", "#AF9AEB", "#9A7EE6"), |
| painting: new WebInspector.TimelineCategory("painting", WebInspector.UIString("Painting"), 2, "#5FA050", "#8DC286", "#71B363"), |
| other: new WebInspector.TimelineCategory("other", WebInspector.UIString("Other"), -1, "#BBBBBB", "#DDDDDD", "#EEEEEE") |
| }; |
| return WebInspector.TimelinePresentationModel._categories; |
| }; |
| |
| |
| WebInspector.TimelinePresentationModel._initRecordStyles = function() |
| { |
| if (WebInspector.TimelinePresentationModel._recordStylesMap) |
| return WebInspector.TimelinePresentationModel._recordStylesMap; |
| |
| var recordTypes = WebInspector.TimelineModel.RecordType; |
| var categories = WebInspector.TimelinePresentationModel.categories(); |
| |
| var recordStyles = {}; |
| recordStyles[recordTypes.Root] = { title: "#root", category: categories["loading"] }; |
| recordStyles[recordTypes.Program] = { title: WebInspector.UIString("Other"), category: categories["other"] }; |
| recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: categories["scripting"] }; |
| recordStyles[recordTypes.BeginFrame] = { title: WebInspector.UIString("Frame Start"), category: categories["rendering"] }; |
| recordStyles[recordTypes.ScheduleStyleRecalculation] = { title: WebInspector.UIString("Schedule Style Recalculation"), category: categories["rendering"] }; |
| recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: categories["rendering"] }; |
| recordStyles[recordTypes.InvalidateLayout] = { title: WebInspector.UIString("Invalidate Layout"), category: categories["rendering"] }; |
| recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: categories["rendering"] }; |
| recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: categories["painting"] }; |
| recordStyles[recordTypes.Rasterize] = { title: WebInspector.UIString("Rasterize"), category: categories["painting"] }; |
| recordStyles[recordTypes.ScrollLayer] = { title: WebInspector.UIString("Scroll"), category: categories["rendering"] }; |
| recordStyles[recordTypes.DecodeImage] = { title: WebInspector.UIString("Image Decode"), category: categories["painting"] }; |
| recordStyles[recordTypes.ResizeImage] = { title: WebInspector.UIString("Image Resize"), category: categories["painting"] }; |
| recordStyles[recordTypes.CompositeLayers] = { title: WebInspector.UIString("Composite Layers"), category: categories["painting"] }; |
| recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse HTML"), category: categories["loading"] }; |
| recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: categories["scripting"] }; |
| recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: categories["scripting"] }; |
| recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: categories["scripting"] }; |
| recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: categories["scripting"] }; |
| recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: categories["scripting"] }; |
| recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: categories["scripting"] }; |
| recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: categories["loading"] }; |
| recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: categories["loading"] }; |
| recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: categories["loading"] }; |
| recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: categories["scripting"] }; |
| recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: categories["loading"] }; |
| recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: categories["scripting"] }; |
| recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContentLoaded event"), category: categories["scripting"] }; |
| recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: categories["scripting"] }; |
| recordStyles[recordTypes.TimeStamp] = { title: WebInspector.UIString("Stamp"), category: categories["scripting"] }; |
| recordStyles[recordTypes.Time] = { title: WebInspector.UIString("Time"), category: categories["scripting"] }; |
| recordStyles[recordTypes.TimeEnd] = { title: WebInspector.UIString("Time End"), category: categories["scripting"] }; |
| recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: categories["loading"] }; |
| recordStyles[recordTypes.RequestAnimationFrame] = { title: WebInspector.UIString("Request Animation Frame"), category: categories["scripting"] }; |
| recordStyles[recordTypes.CancelAnimationFrame] = { title: WebInspector.UIString("Cancel Animation Frame"), category: categories["scripting"] }; |
| recordStyles[recordTypes.FireAnimationFrame] = { title: WebInspector.UIString("Animation Frame Fired"), category: categories["scripting"] }; |
| recordStyles[recordTypes.WebSocketCreate] = { title: WebInspector.UIString("Create WebSocket"), category: categories["scripting"] }; |
| recordStyles[recordTypes.WebSocketSendHandshakeRequest] = { title: WebInspector.UIString("Send WebSocket Handshake"), category: categories["scripting"] }; |
| recordStyles[recordTypes.WebSocketReceiveHandshakeResponse] = { title: WebInspector.UIString("Receive WebSocket Handshake"), category: categories["scripting"] }; |
| recordStyles[recordTypes.WebSocketDestroy] = { title: WebInspector.UIString("Destroy WebSocket"), category: categories["scripting"] }; |
| |
| WebInspector.TimelinePresentationModel._recordStylesMap = recordStyles; |
| return recordStyles; |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.recordStyle = function(record) |
| { |
| var recordStyles = WebInspector.TimelinePresentationModel._initRecordStyles(); |
| var result = recordStyles[record.type]; |
| if (!result) { |
| result = { |
| title: WebInspector.UIString("Unknown: %s", record.type), |
| category: WebInspector.TimelinePresentationModel.categories()["other"] |
| }; |
| recordStyles[record.type] = result; |
| } |
| return result; |
| } |
| |
| WebInspector.TimelinePresentationModel.categoryForRecord = function(record) |
| { |
| return WebInspector.TimelinePresentationModel.recordStyle(record).category; |
| } |
| |
| WebInspector.TimelinePresentationModel.isEventDivider = function(record) |
| { |
| var recordTypes = WebInspector.TimelineModel.RecordType; |
| if (record.type === recordTypes.TimeStamp) |
| return true; |
| if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) { |
| if (record.data && ((typeof record.data.isMainFrame) === "boolean")) |
| return record.data.isMainFrame; |
| } |
| return false; |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback) |
| { |
| if (!recordsArray) |
| return; |
| var stack = [{array: recordsArray, index: 0}]; |
| while (stack.length) { |
| var entry = stack[stack.length - 1]; |
| var records = entry.array; |
| if (entry.index < records.length) { |
| var record = records[entry.index]; |
| if (preOrderCallback && preOrderCallback(record)) |
| return; |
| if (record.children) |
| stack.push({array: record.children, index: 0, record: record}); |
| else if (postOrderCallback && postOrderCallback(record)) |
| return; |
| ++entry.index; |
| } else { |
| if (entry.record && postOrderCallback && postOrderCallback(entry.record)) |
| return; |
| stack.pop(); |
| } |
| } |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.needsPreviewElement = function(recordType) |
| { |
| if (!recordType) |
| return false; |
| const recordTypes = WebInspector.TimelineModel.RecordType; |
| switch (recordType) { |
| case recordTypes.ScheduleResourceRequest: |
| case recordTypes.ResourceSendRequest: |
| case recordTypes.ResourceReceiveResponse: |
| case recordTypes.ResourceReceivedData: |
| case recordTypes.ResourceFinish: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.createEventDivider = function(recordType, title) |
| { |
| var eventDivider = document.createElement("div"); |
| eventDivider.className = "resources-event-divider"; |
| var recordTypes = WebInspector.TimelineModel.RecordType; |
| |
| if (recordType === recordTypes.MarkDOMContent) |
| eventDivider.className += " resources-blue-divider"; |
| else if (recordType === recordTypes.MarkLoad) |
| eventDivider.className += " resources-red-divider"; |
| else if (recordType === recordTypes.TimeStamp) |
| eventDivider.className += " resources-orange-divider"; |
| else if (recordType === recordTypes.BeginFrame) |
| eventDivider.className += " timeline-frame-divider"; |
| |
| if (title) |
| eventDivider.title = title; |
| |
| return eventDivider; |
| } |
| |
| WebInspector.TimelinePresentationModel._hiddenRecords = { } |
| WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkDOMContent] = 1; |
| WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkLoad] = 1; |
| WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation] = 1; |
| WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.InvalidateLayout] = 1; |
| |
| WebInspector.TimelinePresentationModel.prototype = { |
| |
| addFilter: function(filter) |
| { |
| this._filters.push(filter); |
| }, |
| |
| |
| removeFilter: function(filter) |
| { |
| var index = this._filters.indexOf(filter); |
| if (index !== -1) |
| this._filters.splice(index, 1); |
| }, |
| |
| rootRecord: function() |
| { |
| return this._rootRecord; |
| }, |
| |
| frames: function() |
| { |
| return this._frames; |
| }, |
| |
| reset: function() |
| { |
| this._linkifier.reset(); |
| this._rootRecord = new WebInspector.TimelinePresentationModel.Record(this, { type: WebInspector.TimelineModel.RecordType.Root }, null, null, null, false); |
| this._sendRequestRecords = {}; |
| this._scheduledResourceRequests = {}; |
| this._timerRecords = {}; |
| this._requestAnimationFrameRecords = {}; |
| this._eventDividerRecords = []; |
| this._timeRecords = {}; |
| this._timeRecordStack = []; |
| this._frames = []; |
| this._minimumRecordTime = -1; |
| this._layoutInvalidateStack = {}; |
| this._lastScheduleStyleRecalculation = {}; |
| this._webSocketCreateRecords = {}; |
| this._coalescingBuckets = {}; |
| }, |
| |
| addFrame: function(frame) |
| { |
| this._frames.push(frame); |
| }, |
| |
| addRecord: function(record) |
| { |
| if (this._minimumRecordTime === -1 || record.startTime < this._minimumRecordTime) |
| this._minimumRecordTime = WebInspector.TimelineModel.startTimeInSeconds(record); |
| |
| var records; |
| if (record.type === WebInspector.TimelineModel.RecordType.Program) |
| records = record.children; |
| else |
| records = [record]; |
| |
| var formattedRecords = []; |
| var recordsCount = records.length; |
| for (var i = 0; i < recordsCount; ++i) |
| formattedRecords.push(this._innerAddRecord(records[i], this._rootRecord)); |
| return formattedRecords; |
| }, |
| |
| _innerAddRecord: function(record, parentRecord) |
| { |
| const recordTypes = WebInspector.TimelineModel.RecordType; |
| var isHiddenRecord = record.type in WebInspector.TimelinePresentationModel._hiddenRecords; |
| var origin; |
| var coalescingBucket; |
| |
| if (!isHiddenRecord) { |
| var newParentRecord = this._findParentRecord(record); |
| if (newParentRecord) { |
| origin = parentRecord; |
| parentRecord = newParentRecord; |
| } |
| if (parentRecord === this._rootRecord) { |
| |
| coalescingBucket = record.thread ? record.type : "mainThread"; |
| var coalescedRecord = this._findCoalescedParent(record, coalescingBucket); |
| if (coalescedRecord) { |
| if (!origin) |
| origin = parentRecord; |
| parentRecord = coalescedRecord; |
| } |
| } |
| } |
| |
| var children = record.children; |
| var scriptDetails; |
| if (record.data && record.data["scriptName"]) { |
| scriptDetails = { |
| scriptName: record.data["scriptName"], |
| scriptLine: record.data["scriptLine"] |
| } |
| }; |
| |
| if ((record.type === recordTypes.TimerFire || record.type === recordTypes.FireAnimationFrame) && children && children.length) { |
| var childRecord = children[0]; |
| if (childRecord.type === recordTypes.FunctionCall) { |
| scriptDetails = { |
| scriptName: childRecord.data["scriptName"], |
| scriptLine: childRecord.data["scriptLine"] |
| }; |
| children = childRecord.children.concat(children.slice(1)); |
| } |
| } |
| |
| var formattedRecord = new WebInspector.TimelinePresentationModel.Record(this, record, parentRecord, origin, scriptDetails, isHiddenRecord); |
| |
| if (WebInspector.TimelinePresentationModel.isEventDivider(formattedRecord)) |
| this._eventDividerRecords.push(formattedRecord); |
| |
| if (isHiddenRecord) |
| return formattedRecord; |
| |
| formattedRecord.collapsed = parentRecord === this._rootRecord; |
| if (coalescingBucket) |
| this._coalescingBuckets[coalescingBucket] = formattedRecord; |
| |
| var childrenCount = children ? children.length : 0; |
| for (var i = 0; i < childrenCount; ++i) |
| this._innerAddRecord(children[i], formattedRecord); |
| |
| formattedRecord.calculateAggregatedStats(); |
| |
| if (origin) |
| this._updateAncestorStats(formattedRecord); |
| |
| if (parentRecord.coalesced && parentRecord.startTime > formattedRecord.startTime) |
| parentRecord._record.startTime = record.startTime; |
| |
| origin = formattedRecord.origin(); |
| if (!origin.isRoot() && !origin.coalesced) |
| origin.selfTime -= formattedRecord.endTime - formattedRecord.startTime; |
| return formattedRecord; |
| }, |
| |
| |
| _updateAncestorStats: function(record) |
| { |
| var lastChildEndTime = record.lastChildEndTime; |
| var aggregatedStats = record.aggregatedStats; |
| for (var currentRecord = record.parent; currentRecord && !currentRecord.isRoot(); currentRecord = currentRecord.parent) { |
| currentRecord._cpuTime += record._cpuTime; |
| if (currentRecord.lastChildEndTime < lastChildEndTime) |
| currentRecord.lastChildEndTime = lastChildEndTime; |
| for (var category in aggregatedStats) |
| currentRecord.aggregatedStats[category] += aggregatedStats[category]; |
| } |
| }, |
| |
| |
| _findCoalescedParent: function(record, bucket) |
| { |
| const coalescingThresholdSeconds = 0.001; |
| |
| var lastRecord = this._coalescingBuckets[bucket]; |
| var startTime = WebInspector.TimelineModel.startTimeInSeconds(record); |
| var endTime = WebInspector.TimelineModel.endTimeInSeconds(record); |
| if (!lastRecord || lastRecord.type !== record.type) |
| return null; |
| if (lastRecord.endTime + coalescingThresholdSeconds < startTime) |
| return null; |
| if (endTime + coalescingThresholdSeconds < lastRecord.startTime) |
| return null; |
| if (lastRecord.parent.coalesced) |
| return lastRecord.parent; |
| |
| if (lastRecord.parent !== this._rootRecord) |
| return null; |
| return this._replaceWithCoalescedRecord(lastRecord); |
| }, |
| |
| |
| _replaceWithCoalescedRecord: function(record) |
| { |
| var rawRecord = { |
| type: record._record.type, |
| startTime: record._record.startTime, |
| endTime: record._record.endTime, |
| data: { } |
| }; |
| if (record._record.thread) |
| rawRecord.thread = "aggregated"; |
| var coalescedRecord = new WebInspector.TimelinePresentationModel.Record(this, rawRecord, null, null, null, false); |
| var parent = record.parent; |
| |
| coalescedRecord.coalesced = true; |
| coalescedRecord.collapsed = true; |
| coalescedRecord._children.push(record); |
| record.parent = coalescedRecord; |
| coalescedRecord.calculateAggregatedStats(); |
| if (record.hasWarning || record.childHasWarning) |
| coalescedRecord.childHasWarning = true; |
| |
| coalescedRecord.parent = parent; |
| parent._children[parent._children.indexOf(record)] = coalescedRecord; |
| return coalescedRecord; |
| }, |
| |
| _findParentRecord: function(record) |
| { |
| if (!this._glueRecords) |
| return null; |
| var recordTypes = WebInspector.TimelineModel.RecordType; |
| |
| switch (record.type) { |
| case recordTypes.ResourceReceiveResponse: |
| case recordTypes.ResourceFinish: |
| case recordTypes.ResourceReceivedData: |
| return this._sendRequestRecords[record.data["requestId"]]; |
| |
| case recordTypes.ResourceSendRequest: |
| return this._rootRecord; |
| |
| case recordTypes.TimerFire: |
| return this._timerRecords[record.data["timerId"]]; |
| |
| case recordTypes.ResourceSendRequest: |
| return this._scheduledResourceRequests[record.data["url"]]; |
| |
| case recordTypes.FireAnimationFrame: |
| return this._requestAnimationFrameRecords[record.data["id"]]; |
| |
| case recordTypes.Time: |
| return this._rootRecord; |
| |
| case recordTypes.TimeEnd: |
| return this._timeRecords[record.data["message"]]; |
| } |
| }, |
| |
| setGlueRecords: function(glue) |
| { |
| this._glueRecords = glue; |
| }, |
| |
| invalidateFilteredRecords: function() |
| { |
| delete this._filteredRecords; |
| }, |
| |
| filteredRecords: function() |
| { |
| if (this._filteredRecords) |
| return this._filteredRecords; |
| |
| var recordsInWindow = []; |
| |
| var stack = [{children: this._rootRecord.children, index: 0, parentIsCollapsed: false}]; |
| while (stack.length) { |
| var entry = stack[stack.length - 1]; |
| var records = entry.children; |
| if (records && entry.index < records.length) { |
| var record = records[entry.index]; |
| ++entry.index; |
| |
| if (this.isVisible(record)) { |
| ++record.parent._invisibleChildrenCount; |
| if (!entry.parentIsCollapsed) |
| recordsInWindow.push(record); |
| } |
| |
| record._invisibleChildrenCount = 0; |
| |
| stack.push({children: record.children, |
| index: 0, |
| parentIsCollapsed: (entry.parentIsCollapsed || record.collapsed), |
| parentRecord: record, |
| windowLengthBeforeChildrenTraversal: recordsInWindow.length}); |
| } else { |
| stack.pop(); |
| if (entry.parentRecord) |
| entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal; |
| } |
| } |
| |
| this._filteredRecords = recordsInWindow; |
| return recordsInWindow; |
| }, |
| |
| filteredFrames: function(startTime, endTime) |
| { |
| function compareStartTime(value, object) |
| { |
| return value - object.startTime; |
| } |
| function compareEndTime(value, object) |
| { |
| return value - object.endTime; |
| } |
| var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, this._frames, compareStartTime); |
| var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, this._frames, compareEndTime); |
| while (lastFrame < this._frames.length && this._frames[lastFrame].endTime <= endTime) |
| ++lastFrame; |
| return this._frames.slice(firstFrame, lastFrame); |
| }, |
| |
| eventDividerRecords: function() |
| { |
| return this._eventDividerRecords; |
| }, |
| |
| isVisible: function(record) |
| { |
| for (var i = 0; i < this._filters.length; ++i) { |
| if (!this._filters[i].accept(record)) |
| return false; |
| } |
| return true; |
| }, |
| |
| |
| generateMainThreadBarPopupContent: function(info) |
| { |
| var firstTaskIndex = info.firstTaskIndex; |
| var lastTaskIndex = info.lastTaskIndex; |
| var tasks = info.tasks; |
| var messageCount = lastTaskIndex - firstTaskIndex + 1; |
| var cpuTime = 0; |
| |
| for (var i = firstTaskIndex; i <= lastTaskIndex; ++i) { |
| var task = tasks[i]; |
| cpuTime += task.endTime - task.startTime; |
| } |
| var startTime = tasks[firstTaskIndex].startTime; |
| var endTime = tasks[lastTaskIndex].endTime; |
| var duration = endTime - startTime; |
| var offset = this._minimumRecordTime; |
| |
| var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("CPU")); |
| var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(duration, true), |
| Number.secondsToString(startTime - offset, true)); |
| contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText); |
| contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(cpuTime, true)); |
| contentHelper.appendTextRow(WebInspector.UIString("Message Count"), messageCount); |
| return contentHelper.contentTable(); |
| }, |
| |
| __proto__: WebInspector.Object.prototype |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.Record = function(presentationModel, record, parentRecord, origin, scriptDetails, hidden) |
| { |
| this._linkifier = presentationModel._linkifier; |
| this._aggregatedStats = {}; |
| this._record = record; |
| this._children = []; |
| if (!hidden && parentRecord) { |
| this.parent = parentRecord; |
| if (this.isBackground) |
| WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(parentRecord, this); |
| else |
| parentRecord.children.push(this); |
| } |
| if (origin) |
| this._origin = origin; |
| |
| this._selfTime = this.endTime - this.startTime; |
| this._lastChildEndTime = this.endTime; |
| this._startTimeOffset = this.startTime - presentationModel._minimumRecordTime; |
| |
| if (record.data && record.data["url"]) |
| this.url = record.data["url"]; |
| if (scriptDetails) { |
| this.scriptName = scriptDetails.scriptName; |
| this.scriptLine = scriptDetails.scriptLine; |
| } |
| if (parentRecord && parentRecord.callSiteStackTrace) |
| this.callSiteStackTrace = parentRecord.callSiteStackTrace; |
| |
| var recordTypes = WebInspector.TimelineModel.RecordType; |
| switch (record.type) { |
| case recordTypes.ResourceSendRequest: |
| |
| presentationModel._sendRequestRecords[record.data["requestId"]] = this; |
| break; |
| |
| case recordTypes.ScheduleResourceRequest: |
| presentationModel._scheduledResourceRequests[record.data["url"]] = this; |
| break; |
| |
| case recordTypes.ResourceReceiveResponse: |
| var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]]; |
| if (sendRequestRecord) { |
| this.url = sendRequestRecord.url; |
| |
| sendRequestRecord._refreshDetails(); |
| if (sendRequestRecord.parent !== presentationModel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest) |
| sendRequestRecord.parent._refreshDetails(); |
| } |
| break; |
| |
| case recordTypes.ResourceReceivedData: |
| case recordTypes.ResourceFinish: |
| var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]]; |
| if (sendRequestRecord) |
| this.url = sendRequestRecord.url; |
| break; |
| |
| case recordTypes.TimerInstall: |
| this.timeout = record.data["timeout"]; |
| this.singleShot = record.data["singleShot"]; |
| presentationModel._timerRecords[record.data["timerId"]] = this; |
| break; |
| |
| case recordTypes.TimerFire: |
| var timerInstalledRecord = presentationModel._timerRecords[record.data["timerId"]]; |
| if (timerInstalledRecord) { |
| this.callSiteStackTrace = timerInstalledRecord.stackTrace; |
| this.timeout = timerInstalledRecord.timeout; |
| this.singleShot = timerInstalledRecord.singleShot; |
| } |
| break; |
| |
| case recordTypes.RequestAnimationFrame: |
| presentationModel._requestAnimationFrameRecords[record.data["id"]] = this; |
| break; |
| |
| case recordTypes.FireAnimationFrame: |
| var requestAnimationRecord = presentationModel._requestAnimationFrameRecords[record.data["id"]]; |
| if (requestAnimationRecord) |
| this.callSiteStackTrace = requestAnimationRecord.stackTrace; |
| break; |
| |
| case recordTypes.Time: |
| var message = record.data["message"]; |
| var oldReference = presentationModel._timeRecords[message]; |
| if (oldReference) |
| break; |
| presentationModel._timeRecords[message] = this; |
| if (origin) |
| presentationModel._timeRecordStack.push(this); |
| break; |
| |
| case recordTypes.TimeEnd: |
| var message = record.data["message"]; |
| var timeRecord = presentationModel._timeRecords[message]; |
| delete presentationModel._timeRecords[message]; |
| if (timeRecord) { |
| this.timeRecord = timeRecord; |
| timeRecord.timeEndRecord = this; |
| var intervalDuration = this.startTime - timeRecord.startTime; |
| this.intervalDuration = intervalDuration; |
| timeRecord.intervalDuration = intervalDuration; |
| if (!origin) |
| break; |
| var recordStack = presentationModel._timeRecordStack; |
| recordStack.splice(recordStack.indexOf(timeRecord), 1); |
| for (var index = recordStack.length; index; --index) { |
| var openRecord = recordStack[index - 1]; |
| if (openRecord.startTime > timeRecord.startTime) |
| continue; |
| WebInspector.TimelinePresentationModel.adoptRecord(openRecord, timeRecord); |
| break; |
| } |
| } |
| break; |
| |
| case recordTypes.ScheduleStyleRecalculation: |
| presentationModel._lastScheduleStyleRecalculation[this.frameId] = this; |
| break; |
| |
| case recordTypes.RecalculateStyles: |
| var scheduleStyleRecalculationRecord = presentationModel._lastScheduleStyleRecalculation[this.frameId]; |
| if (!scheduleStyleRecalculationRecord) |
| break; |
| this.callSiteStackTrace = scheduleStyleRecalculationRecord.stackTrace; |
| break; |
| |
| case recordTypes.InvalidateLayout: |
| |
| |
| var styleRecalcStack; |
| if (!presentationModel._layoutInvalidateStack[this.frameId]) { |
| for (var outerRecord = parentRecord; outerRecord; outerRecord = record.parent) { |
| if (outerRecord.type === recordTypes.RecalculateStyles) { |
| styleRecalcStack = outerRecord.callSiteStackTrace; |
| break; |
| } |
| } |
| } |
| presentationModel._layoutInvalidateStack[this.frameId] = styleRecalcStack || this.stackTrace; |
| break; |
| |
| case recordTypes.Layout: |
| var layoutInvalidateStack = presentationModel._layoutInvalidateStack[this.frameId]; |
| if (layoutInvalidateStack) |
| this.callSiteStackTrace = layoutInvalidateStack; |
| if (this.stackTrace) |
| this.setHasWarning(); |
| presentationModel._layoutInvalidateStack[this.frameId] = null; |
| this.highlightQuad = record.data.root || WebInspector.TimelinePresentationModel.quadFromRectData(record.data); |
| break; |
| |
| case recordTypes.Paint: |
| this.highlightQuad = record.data.clip || WebInspector.TimelinePresentationModel.quadFromRectData(record.data); |
| break; |
| |
| case recordTypes.WebSocketCreate: |
| this.webSocketURL = record.data["url"]; |
| if (typeof record.data["webSocketProtocol"] !== "undefined") |
| this.webSocketProtocol = record.data["webSocketProtocol"]; |
| presentationModel._webSocketCreateRecords[record.data["identifier"]] = this; |
| break; |
| |
| case recordTypes.WebSocketSendHandshakeRequest: |
| case recordTypes.WebSocketReceiveHandshakeResponse: |
| case recordTypes.WebSocketDestroy: |
| var webSocketCreateRecord = presentationModel._webSocketCreateRecords[record.data["identifier"]]; |
| if (webSocketCreateRecord) { |
| this.webSocketURL = webSocketCreateRecord.webSocketURL; |
| if (typeof webSocketCreateRecord.webSocketProtocol !== "undefined") |
| this.webSocketProtocol = webSocketCreateRecord.webSocketProtocol; |
| } |
| break; |
| } |
| } |
| |
| WebInspector.TimelinePresentationModel.adoptRecord = function(newParent, record) |
| { |
| record.parent.children.splice(record.parent.children.indexOf(record)); |
| WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(newParent, record); |
| record.parent = newParent; |
| } |
| |
| WebInspector.TimelinePresentationModel.insertRetrospectiveRecord = function(parent, record) |
| { |
| function compareStartTime(value, record) |
| { |
| return value < record.startTime ? -1 : 1; |
| } |
| |
| parent.children.splice(insertionIndexForObjectInListSortedByFunction(record.startTime, parent.children, compareStartTime), 0, record); |
| } |
| |
| WebInspector.TimelinePresentationModel.Record.prototype = { |
| get lastChildEndTime() |
| { |
| return this._lastChildEndTime; |
| }, |
| |
| set lastChildEndTime(time) |
| { |
| this._lastChildEndTime = time; |
| }, |
| |
| get selfTime() |
| { |
| return this.coalesced ? this._lastChildEndTime - this.startTime : this._selfTime; |
| }, |
| |
| set selfTime(time) |
| { |
| this._selfTime = time; |
| }, |
| |
| get cpuTime() |
| { |
| return this._cpuTime; |
| }, |
| |
| |
| isRoot: function() |
| { |
| return this.type === WebInspector.TimelineModel.RecordType.Root; |
| }, |
| |
| |
| origin: function() |
| { |
| return this._origin || this.parent; |
| }, |
| |
| |
| get children() |
| { |
| return this._children; |
| }, |
| |
| |
| get visibleChildrenCount() |
| { |
| return this._visibleChildrenCount || 0; |
| }, |
| |
| |
| get invisibleChildrenCount() |
| { |
| return this._invisibleChildrenCount || 0; |
| }, |
| |
| |
| get category() |
| { |
| return WebInspector.TimelinePresentationModel.recordStyle(this._record).category |
| }, |
| |
| |
| get title() |
| { |
| return this.type === WebInspector.TimelineModel.RecordType.TimeStamp ? this._record.data["message"] : |
| WebInspector.TimelinePresentationModel.recordStyle(this._record).title; |
| }, |
| |
| |
| get startTime() |
| { |
| return WebInspector.TimelineModel.startTimeInSeconds(this._record); |
| }, |
| |
| |
| get endTime() |
| { |
| return WebInspector.TimelineModel.endTimeInSeconds(this._record); |
| }, |
| |
| |
| get isBackground() |
| { |
| return !!this._record.thread; |
| }, |
| |
| |
| get data() |
| { |
| return this._record.data; |
| }, |
| |
| |
| get type() |
| { |
| return this._record.type; |
| }, |
| |
| |
| get frameId() |
| { |
| return this._record.frameId; |
| }, |
| |
| |
| get usedHeapSizeDelta() |
| { |
| return this._record.usedHeapSizeDelta || 0; |
| }, |
| |
| |
| get usedHeapSize() |
| { |
| return this._record.usedHeapSize; |
| }, |
| |
| |
| get stackTrace() |
| { |
| if (this._record.stackTrace && this._record.stackTrace.length) |
| return this._record.stackTrace; |
| return null; |
| }, |
| |
| containsTime: function(time) |
| { |
| return this.startTime <= time && time <= this.endTime; |
| }, |
| |
| |
| generatePopupContent: function(callback) |
| { |
| if (WebInspector.TimelinePresentationModel.needsPreviewElement(this.type)) |
| WebInspector.DOMPresentationUtils.buildImagePreviewContents(this.url, false, this._generatePopupContentWithImagePreview.bind(this, callback)); |
| else |
| this._generatePopupContentWithImagePreview(callback); |
| }, |
| |
| |
| _generatePopupContentWithImagePreview: function(callback, previewElement) |
| { |
| var contentHelper = new WebInspector.PopoverContentHelper(this.title); |
| var text = WebInspector.UIString("%s (at %s)", Number.secondsToString(this._lastChildEndTime - this.startTime, true), |
| Number.secondsToString(this._startTimeOffset)); |
| contentHelper.appendTextRow(WebInspector.UIString("Duration"), text); |
| |
| if (this._children.length) { |
| if (!this.coalesced) |
| contentHelper.appendTextRow(WebInspector.UIString("Self Time"), Number.secondsToString(this._selfTime, true)); |
| contentHelper.appendTextRow(WebInspector.UIString("CPU Time"), Number.secondsToString(this._cpuTime, true)); |
| contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"), |
| WebInspector.TimelinePresentationModel._generateAggregatedInfo(this._aggregatedStats)); |
| } |
| |
| if (this.coalesced) { |
| callback(contentHelper.contentTable()); |
| return; |
| } |
| const recordTypes = WebInspector.TimelineModel.RecordType; |
| |
| |
| var callSiteStackTraceLabel; |
| var callStackLabel; |
| |
| switch (this.type) { |
| case recordTypes.GCEvent: |
| contentHelper.appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data["usedHeapSizeDelta"])); |
| break; |
| case recordTypes.TimerFire: |
| callSiteStackTraceLabel = WebInspector.UIString("Timer installed"); |
| |
| |
| case recordTypes.TimerInstall: |
| case recordTypes.TimerRemove: |
| contentHelper.appendTextRow(WebInspector.UIString("Timer ID"), this.data["timerId"]); |
| if (typeof this.timeout === "number") { |
| contentHelper.appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000)); |
| contentHelper.appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot); |
| } |
| break; |
| case recordTypes.FireAnimationFrame: |
| callSiteStackTraceLabel = WebInspector.UIString("Animation frame requested"); |
| contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), this.data["id"]); |
| break; |
| case recordTypes.FunctionCall: |
| contentHelper.appendElementRow(WebInspector.UIString("Location"), this._linkifyScriptLocation()); |
| break; |
| case recordTypes.ScheduleResourceRequest: |
| case recordTypes.ResourceSendRequest: |
| case recordTypes.ResourceReceiveResponse: |
| case recordTypes.ResourceReceivedData: |
| case recordTypes.ResourceFinish: |
| contentHelper.appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(this.url)); |
| if (previewElement) |
| contentHelper.appendElementRow(WebInspector.UIString("Preview"), previewElement); |
| if (this.data["requestMethod"]) |
| contentHelper.appendTextRow(WebInspector.UIString("Request Method"), this.data["requestMethod"]); |
| if (typeof this.data["statusCode"] === "number") |
| contentHelper.appendTextRow(WebInspector.UIString("Status Code"), this.data["statusCode"]); |
| if (this.data["mimeType"]) |
| contentHelper.appendTextRow(WebInspector.UIString("MIME Type"), this.data["mimeType"]); |
| if (this.data["encodedDataLength"]) |
| contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", this.data["encodedDataLength"])); |
| break; |
| case recordTypes.EvaluateScript: |
| if (this.data && this.url) |
| contentHelper.appendElementRow(WebInspector.UIString("Script"), this._linkifyLocation(this.url, this.data["lineNumber"])); |
| break; |
| case recordTypes.Paint: |
| var clip = this.data["clip"]; |
| if (clip) { |
| contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", clip[0], clip[1])); |
| var clipWidth = WebInspector.TimelinePresentationModel.quadWidth(clip); |
| var clipHeight = WebInspector.TimelinePresentationModel.quadHeight(clip); |
| contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d × %d", clipWidth, clipHeight)); |
| } else { |
| |
| if (typeof this.data["x"] !== "undefined" && typeof this.data["y"] !== "undefined") |
| contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data["x"], this.data["y"])); |
| if (typeof this.data["width"] !== "undefined" && typeof this.data["height"] !== "undefined") |
| contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d\u2009\u00d7\u2009%d", this.data["width"], this.data["height"])); |
| } |
| break; |
| case recordTypes.RecalculateStyles: |
| if (this.data["elementCount"]) |
| contentHelper.appendTextRow(WebInspector.UIString("Elements affected"), this.data["elementCount"]); |
| callStackLabel = WebInspector.UIString("Styles recalculation forced"); |
| break; |
| case recordTypes.Layout: |
| if (this.data["dirtyObjects"]) |
| contentHelper.appendTextRow(WebInspector.UIString("Nodes that need layout"), this.data["dirtyObjects"]); |
| if (this.data["totalObjects"]) |
| contentHelper.appendTextRow(WebInspector.UIString("Layout tree size"), this.data["totalObjects"]); |
| if (typeof this.data["partialLayout"] === "boolean") { |
| contentHelper.appendTextRow(WebInspector.UIString("Layout scope"), |
| this.data["partialLayout"] ? WebInspector.UIString("Partial") : WebInspector.UIString("Whole document")); |
| } |
| callSiteStackTraceLabel = WebInspector.UIString("Layout invalidated"); |
| if (this.stackTrace) { |
| callStackLabel = WebInspector.UIString("Layout forced"); |
| contentHelper.appendTextRow(WebInspector.UIString("Note"), WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck.")); |
| } |
| break; |
| case recordTypes.Time: |
| case recordTypes.TimeEnd: |
| contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]); |
| if (typeof this.intervalDuration === "number") |
| contentHelper.appendTextRow(WebInspector.UIString("Interval Duration"), Number.secondsToString(this.intervalDuration, true)); |
| break; |
| case recordTypes.WebSocketCreate: |
| case recordTypes.WebSocketSendHandshakeRequest: |
| case recordTypes.WebSocketReceiveHandshakeResponse: |
| case recordTypes.WebSocketDestroy: |
| if (typeof this.webSocketURL !== "undefined") |
| contentHelper.appendTextRow(WebInspector.UIString("URL"), this.webSocketURL); |
| if (typeof this.webSocketProtocol !== "undefined") |
| contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"), this.webSocketProtocol); |
| if (typeof this.data["message"] !== "undefined") |
| contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]) |
| break; |
| default: |
| if (this.detailsNode()) |
| contentHelper.appendElementRow(WebInspector.UIString("Details"), this.detailsNode().childNodes[1].cloneNode()); |
| break; |
| } |
| |
| if (this.scriptName && this.type !== recordTypes.FunctionCall) |
| contentHelper.appendElementRow(WebInspector.UIString("Function Call"), this._linkifyScriptLocation()); |
| |
| if (this.usedHeapSize) { |
| if (this.usedHeapSizeDelta) { |
| var sign = this.usedHeapSizeDelta > 0 ? "+" : "-"; |
| contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"), |
| WebInspector.UIString("%s (%s%s)", Number.bytesToString(this.usedHeapSize), sign, Number.bytesToString(this.usedHeapSizeDelta))); |
| } else if (this.category === WebInspector.TimelinePresentationModel.categories().scripting) |
| contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"), Number.bytesToString(this.usedHeapSize)); |
| } |
| |
| if (this.callSiteStackTrace) |
| contentHelper.appendStackTrace(callSiteStackTraceLabel || WebInspector.UIString("Call Site stack"), this.callSiteStackTrace, this._linkifyCallFrame.bind(this)); |
| |
| if (this.stackTrace) |
| contentHelper.appendStackTrace(callStackLabel || WebInspector.UIString("Call Stack"), this.stackTrace, this._linkifyCallFrame.bind(this)); |
| |
| callback(contentHelper.contentTable()); |
| }, |
| |
| _refreshDetails: function() |
| { |
| delete this._detailsNode; |
| }, |
| |
| |
| detailsNode: function() |
| { |
| if (typeof this._detailsNode === "undefined") { |
| this._detailsNode = this._getRecordDetails(); |
| |
| if (this._detailsNode) { |
| this._detailsNode.insertBefore(document.createTextNode("("), this._detailsNode.firstChild); |
| this._detailsNode.appendChild(document.createTextNode(")")); |
| } |
| } |
| return this._detailsNode; |
| }, |
| |
| _createSpanWithText: function(textContent) |
| { |
| var node = document.createElement("span"); |
| node.textContent = textContent; |
| return node; |
| }, |
| |
| |
| _getRecordDetails: function() |
| { |
| var details; |
| if (this.coalesced) |
| return this._createSpanWithText(WebInspector.UIString("× %d", this.children.length)); |
| |
| switch (this.type) { |
| case WebInspector.TimelineModel.RecordType.GCEvent: |
| details = WebInspector.UIString("%s collected", Number.bytesToString(this.data["usedHeapSizeDelta"])); |
| break; |
| case WebInspector.TimelineModel.RecordType.TimerFire: |
| details = this._linkifyScriptLocation(this.data["timerId"]); |
| break; |
| case WebInspector.TimelineModel.RecordType.FunctionCall: |
| details = this._linkifyScriptLocation(); |
| break; |
| case WebInspector.TimelineModel.RecordType.FireAnimationFrame: |
| details = this._linkifyScriptLocation(this.data["id"]); |
| break; |
| case WebInspector.TimelineModel.RecordType.EventDispatch: |
| details = this.data ? this.data["type"] : null; |
| break; |
| case WebInspector.TimelineModel.RecordType.Paint: |
| var width = this.data.clip ? WebInspector.TimelinePresentationModel.quadWidth(this.data.clip) : this.data.width; |
| var height = this.data.clip ? WebInspector.TimelinePresentationModel.quadHeight(this.data.clip) : this.data.height; |
| if (width && height) |
| details = WebInspector.UIString("%d\u2009\u00d7\u2009%d", width, height); |
| break; |
| case WebInspector.TimelineModel.RecordType.DecodeImage: |
| details = this.data["imageType"]; |
| break; |
| case WebInspector.TimelineModel.RecordType.ResizeImage: |
| details = this.data["cached"] ? WebInspector.UIString("cached") : WebInspector.UIString("non-cached"); |
| break; |
| case WebInspector.TimelineModel.RecordType.TimerInstall: |
| case WebInspector.TimelineModel.RecordType.TimerRemove: |
| details = this._linkifyTopCallFrame(this.data["timerId"]); |
| break; |
| case WebInspector.TimelineModel.RecordType.RequestAnimationFrame: |
| case WebInspector.TimelineModel.RecordType.CancelAnimationFrame: |
| details = this._linkifyTopCallFrame(this.data["id"]); |
| break; |
| case WebInspector.TimelineModel.RecordType.ParseHTML: |
| case WebInspector.TimelineModel.RecordType.RecalculateStyles: |
| details = this._linkifyTopCallFrame(); |
| break; |
| case WebInspector.TimelineModel.RecordType.EvaluateScript: |
| details = this.url ? this._linkifyLocation(this.url, this.data["lineNumber"], 0) : null; |
| break; |
| case WebInspector.TimelineModel.RecordType.XHRReadyStateChange: |
| case WebInspector.TimelineModel.RecordType.XHRLoad: |
| case WebInspector.TimelineModel.RecordType.ScheduleResourceRequest: |
| case WebInspector.TimelineModel.RecordType.ResourceSendRequest: |
| case WebInspector.TimelineModel.RecordType.ResourceReceivedData: |
| case WebInspector.TimelineModel.RecordType.ResourceReceiveResponse: |
| case WebInspector.TimelineModel.RecordType.ResourceFinish: |
| details = WebInspector.displayNameForURL(this.url); |
| break; |
| case WebInspector.TimelineModel.RecordType.Time: |
| case WebInspector.TimelineModel.RecordType.TimeEnd: |
| case WebInspector.TimelineModel.RecordType.TimeStamp: |
| details = this.data["message"]; |
| break; |
| default: |
| details = this._linkifyScriptLocation() || this._linkifyTopCallFrame() || null; |
| break; |
| } |
| |
| if (details) { |
| if (details instanceof Node) |
| details.tabIndex = -1; |
| else |
| return this._createSpanWithText("" + details); |
| } |
| |
| return details || null; |
| }, |
| |
| |
| _linkifyLocation: function(url, lineNumber, columnNumber) |
| { |
| |
| columnNumber = columnNumber ? columnNumber - 1 : 0; |
| return this._linkifier.linkifyLocation(url, lineNumber - 1, columnNumber, "timeline-details"); |
| }, |
| |
| _linkifyCallFrame: function(callFrame) |
| { |
| return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber); |
| }, |
| |
| |
| _linkifyTopCallFrame: function(defaultValue) |
| { |
| if (this.stackTrace) |
| return this._linkifyCallFrame(this.stackTrace[0]); |
| if (this.callSiteStackTrace) |
| return this._linkifyCallFrame(this.callSiteStackTrace[0]); |
| return defaultValue; |
| }, |
| |
| |
| _linkifyScriptLocation: function(defaultValue) |
| { |
| if (this.scriptName) |
| return this._linkifyLocation(this.scriptName, this.scriptLine, 0); |
| else |
| return defaultValue ? "" + defaultValue : null; |
| }, |
| |
| calculateAggregatedStats: function() |
| { |
| this._aggregatedStats = {}; |
| this._cpuTime = this._selfTime; |
| |
| for (var index = this._children.length; index; --index) { |
| var child = this._children[index - 1]; |
| for (var category in child._aggregatedStats) |
| this._aggregatedStats[category] = (this._aggregatedStats[category] || 0) + child._aggregatedStats[category]; |
| } |
| for (var category in this._aggregatedStats) |
| this._cpuTime += this._aggregatedStats[category]; |
| this._aggregatedStats[this.category.name] = (this._aggregatedStats[this.category.name] || 0) + this._selfTime; |
| }, |
| |
| get aggregatedStats() |
| { |
| return this._aggregatedStats; |
| }, |
| |
| setHasWarning: function() |
| { |
| this.hasWarning = true; |
| for (var parent = this.parent; parent && !parent.childHasWarning; parent = parent.parent) |
| parent.childHasWarning = true; |
| } |
| } |
| |
| |
| WebInspector.TimelinePresentationModel._generateAggregatedInfo = function(aggregatedStats) |
| { |
| var cell = document.createElement("span"); |
| cell.className = "timeline-aggregated-info"; |
| for (var index in aggregatedStats) { |
| var label = document.createElement("div"); |
| label.className = "timeline-aggregated-category timeline-" + index; |
| cell.appendChild(label); |
| var text = document.createElement("span"); |
| text.textContent = Number.secondsToString(aggregatedStats[index], true); |
| cell.appendChild(text); |
| } |
| return cell; |
| } |
| |
| WebInspector.TimelinePresentationModel.generatePopupContentForFrame = function(frame) |
| { |
| var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("Frame")); |
| var durationInSeconds = frame.endTime - frame.startTime; |
| var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(frame.endTime - frame.startTime, true), |
| Number.secondsToString(frame.startTimeOffset, true)); |
| contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText); |
| contentHelper.appendTextRow(WebInspector.UIString("FPS"), Math.floor(1 / durationInSeconds)); |
| contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(frame.cpuTime, true)); |
| contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"), |
| WebInspector.TimelinePresentationModel._generateAggregatedInfo(frame.timeByCategory)); |
| |
| return contentHelper.contentTable(); |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics = function(statistics) |
| { |
| |
| function formatTimeAndFPS(time) |
| { |
| return WebInspector.UIString("%s (%.0f FPS)", Number.secondsToString(time, true), 1 / time); |
| } |
| |
| var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("Selected Range")); |
| |
| contentHelper.appendTextRow(WebInspector.UIString("Selected range"), WebInspector.UIString("%s\u2013%s (%d frames)", |
| Number.secondsToString(statistics.startOffset, true), Number.secondsToString(statistics.endOffset, true), statistics.frameCount)); |
| contentHelper.appendTextRow(WebInspector.UIString("Minimum Time"), formatTimeAndFPS(statistics.minDuration)); |
| contentHelper.appendTextRow(WebInspector.UIString("Average Time"), formatTimeAndFPS(statistics.average)); |
| contentHelper.appendTextRow(WebInspector.UIString("Maximum Time"), formatTimeAndFPS(statistics.maxDuration)); |
| contentHelper.appendTextRow(WebInspector.UIString("Standard Deviation"), Number.secondsToString(statistics.stddev, true)); |
| contentHelper.appendElementRow(WebInspector.UIString("Time by category"), |
| WebInspector.TimelinePresentationModel._generateAggregatedInfo(statistics.timeByCategory)); |
| |
| return contentHelper.contentTable(); |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.createFillStyle = function(context, width, height, color0, color1, color2) |
| { |
| var gradient = context.createLinearGradient(0, 0, width, height); |
| gradient.addColorStop(0, color0); |
| gradient.addColorStop(0.25, color1); |
| gradient.addColorStop(0.75, color1); |
| gradient.addColorStop(1, color2); |
| return gradient; |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.createFillStyleForCategory = function(context, width, height, category) |
| { |
| return WebInspector.TimelinePresentationModel.createFillStyle(context, width, height, category.fillColorStop0, category.fillColorStop1, category.borderColor); |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.createStyleRuleForCategory = function(category) |
| { |
| var selector = ".timeline-category-" + category.name + " .timeline-graph-bar, " + |
| ".timeline-category-statusbar-item.timeline-category-" + category.name + " .timeline-category-checkbox, " + |
| ".popover .timeline-" + category.name + ", " + |
| ".timeline-category-" + category.name + " .timeline-tree-icon" |
| |
| return selector + " { background-image: -webkit-linear-gradient(" + |
| category.fillColorStop0 + ", " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + " 75%, " + category.borderColor + ");" + |
| " border-color: " + category.borderColor + |
| "}"; |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.quadWidth = function(quad) |
| { |
| return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2))); |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.quadHeight = function(quad) |
| { |
| return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2))); |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.quadFromRectData = function(data) |
| { |
| if (typeof data["x"] === "undefined" || typeof data["y"] === "undefined") |
| return null; |
| var x0 = data["x"]; |
| var x1 = data["x"] + data["width"]; |
| var y0 = data["y"]; |
| var y1 = data["y"] + data["height"]; |
| return [x0, y0, x1, y0, x1, y1, x0, y1]; |
| } |
| |
| |
| WebInspector.TimelinePresentationModel.Filter = function() |
| { |
| } |
| |
| WebInspector.TimelinePresentationModel.Filter.prototype = { |
| |
| accept: function(record) { return false; } |
| } |
| |
| |
| WebInspector.TimelineCategory = function(name, title, overviewStripGroupIndex, borderColor, fillColorStop0, fillColorStop1) |
| { |
| this.name = name; |
| this.title = title; |
| this.overviewStripGroupIndex = overviewStripGroupIndex; |
| this.borderColor = borderColor; |
| this.fillColorStop0 = fillColorStop0; |
| this.fillColorStop1 = fillColorStop1; |
| this.hidden = false; |
| } |
| |
| WebInspector.TimelineCategory.Events = { |
| VisibilityChanged: "VisibilityChanged" |
| }; |
| |
| WebInspector.TimelineCategory.prototype = { |
| |
| get hidden() |
| { |
| return this._hidden; |
| }, |
| |
| set hidden(hidden) |
| { |
| this._hidden = hidden; |
| this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this); |
| }, |
| |
| __proto__: WebInspector.Object.prototype |
| } |
| ; |
| |
| |
| |
| WebInspector.TimelineFrameController = function(model, overviewPane, presentationModel) |
| { |
| this._lastFrame = null; |
| this._model = model; |
| this._overviewPane = overviewPane; |
| this._presentationModel = presentationModel; |
| this._model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this); |
| this._model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this); |
| |
| var records = model.records; |
| for (var i = 0; i < records.length; ++i) |
| this._addRecord(records[i]); |
| } |
| |
| WebInspector.TimelineFrameController.prototype = { |
| _onRecordAdded: function(event) |
| { |
| this._addRecord(event.data); |
| }, |
| |
| _onRecordsCleared: function() |
| { |
| this._lastFrame = null; |
| }, |
| |
| _addRecord: function(record) |
| { |
| if (record.isBackground) |
| return; |
| var records; |
| var programRecord; |
| if (record.type === WebInspector.TimelineModel.RecordType.Program) { |
| programRecord = record; |
| if (this._lastFrame) |
| this._lastFrame.timeByCategory["other"] += WebInspector.TimelineModel.durationInSeconds(programRecord); |
| records = record["children"] || []; |
| } else |
| records = [record]; |
| records.forEach(this._innerAddRecord.bind(this, programRecord)); |
| }, |
| |
| |
| _innerAddRecord: function(programRecord, record) |
| { |
| var isFrameRecord = record.type === WebInspector.TimelineModel.RecordType.BeginFrame; |
| var programTimeCarryover = isFrameRecord && programRecord ? WebInspector.TimelineModel.endTimeInSeconds(programRecord) - WebInspector.TimelineModel.startTimeInSeconds(record) : 0; |
| if (isFrameRecord && this._lastFrame) |
| this._flushFrame(record, programTimeCarryover); |
| else { |
| if (!this._lastFrame) |
| this._lastFrame = this._createFrame(record, programTimeCarryover); |
| if (!record.thread) |
| WebInspector.TimelineModel.aggregateTimeForRecord(this._lastFrame.timeByCategory, record); |
| var duration = WebInspector.TimelineModel.durationInSeconds(record); |
| this._lastFrame.cpuTime += duration; |
| this._lastFrame.timeByCategory["other"] -= duration; |
| } |
| }, |
| |
| |
| _flushFrame: function(record, programTimeCarryover) |
| { |
| this._lastFrame.endTime = WebInspector.TimelineModel.startTimeInSeconds(record); |
| this._lastFrame.duration = this._lastFrame.endTime - this._lastFrame.startTime; |
| this._lastFrame.timeByCategory["other"] -= programTimeCarryover; |
| |
| |
| this._lastFrame.cpuTime += this._lastFrame.timeByCategory["other"]; |
| this._overviewPane.addFrame(this._lastFrame); |
| this._presentationModel.addFrame(this._lastFrame); |
| this._lastFrame = this._createFrame(record, programTimeCarryover); |
| }, |
| |
| |
| _createFrame: function(record, programTimeCarryover) |
| { |
| var frame = new WebInspector.TimelineFrame(); |
| frame.startTime = WebInspector.TimelineModel.startTimeInSeconds(record); |
| frame.startTimeOffset = this._model.recordOffsetInSeconds(record); |
| frame.timeByCategory["other"] = programTimeCarryover; |
| return frame; |
| }, |
| |
| dispose: function() |
| { |
| this._model.removeEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this); |
| this._model.removeEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this); |
| } |
| } |
| |
| |
| WebInspector.FrameStatistics = function(frames) |
| { |
| this.frameCount = frames.length; |
| this.minDuration = Infinity; |
| this.maxDuration = 0; |
| this.timeByCategory = {}; |
| this.startOffset = frames[0].startTimeOffset; |
| var lastFrame = frames[this.frameCount - 1]; |
| this.endOffset = lastFrame.startTimeOffset + lastFrame.duration; |
| |
| var totalDuration = 0; |
| var sumOfSquares = 0; |
| for (var i = 0; i < this.frameCount; ++i) { |
| var duration = frames[i].duration; |
| totalDuration += duration; |
| sumOfSquares += duration * duration; |
| this.minDuration = Math.min(this.minDuration, duration); |
| this.maxDuration = Math.max(this.maxDuration, duration); |
| WebInspector.TimelineModel.aggregateTimeByCategory(this.timeByCategory, frames[i].timeByCategory); |
| } |
| this.average = totalDuration / this.frameCount; |
| var variance = sumOfSquares / this.frameCount - this.average * this.average; |
| this.stddev = Math.sqrt(variance); |
| } |
| |
| |
| WebInspector.TimelineFrame = function() |
| { |
| this.timeByCategory = {}; |
| this.cpuTime = 0; |
| } |
| ; |
| |
| |
| WebInspector.TimelinePanel = function() |
| { |
| WebInspector.Panel.call(this, "timeline"); |
| this.registerRequiredCSS("timelinePanel.css"); |
| |
| this._model = new WebInspector.TimelineModel(); |
| this._presentationModel = new WebInspector.TimelinePresentationModel(); |
| |
| this._overviewModeSetting = WebInspector.settings.createSetting("timelineOverviewMode", WebInspector.TimelineOverviewPane.Mode.Events); |
| this._glueRecordsSetting = WebInspector.settings.createSetting("timelineGlueRecords", false); |
| |
| this._overviewPane = new WebInspector.TimelineOverviewPane(this._model); |
| this._overviewPane.addEventListener(WebInspector.TimelineOverviewPane.Events.WindowChanged, this._invalidateAndScheduleRefresh.bind(this, false, true)); |
| this._overviewPane.addEventListener(WebInspector.TimelineOverviewPane.Events.ModeChanged, this._overviewModeChanged, this); |
| this._overviewPane.show(this.element); |
| |
| this.element.addEventListener("contextmenu", this._contextMenu.bind(this), false); |
| |
| this.element.addStyleClass("split-view-vertical"); |
| |
| this._sidebarBackgroundElement = document.createElement("div"); |
| this._sidebarBackgroundElement.className = "sidebar split-view-sidebar split-view-contents-first timeline-sidebar-background"; |
| this.element.appendChild(this._sidebarBackgroundElement); |
| |
| this.createSidebarViewWithTree(); |
| this.element.appendChild(this.splitView.resizerElement()); |
| |
| this._containerElement = this.splitView.element; |
| this._containerElement.tabIndex = 0; |
| this._containerElement.id = "timeline-container"; |
| this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false); |
| |
| this._timelineMemorySplitter = this.element.createChild("div"); |
| this._timelineMemorySplitter.id = "timeline-memory-splitter"; |
| WebInspector.installDragHandle(this._timelineMemorySplitter, this._startSplitterDragging.bind(this), this._splitterDragging.bind(this), this._endSplitterDragging.bind(this), "ns-resize"); |
| this._timelineMemorySplitter.addStyleClass("hidden"); |
| this._includeDomCounters = false; |
| this._includeNativeMemoryStatistics = false; |
| if (WebInspector.experimentsSettings.nativeMemoryTimeline.isEnabled()) { |
| this._memoryStatistics = new WebInspector.NativeMemoryGraph(this, this._model, this.splitView.sidebarWidth()); |
| this._includeNativeMemoryStatistics = true; |
| } else { |
| this._memoryStatistics = new WebInspector.DOMCountersGraph(this, this._model, this.splitView.sidebarWidth()); |
| this._includeDomCounters = true; |
| } |
| WebInspector.settings.memoryCounterGraphsHeight = WebInspector.settings.createSetting("memoryCounterGraphsHeight", 150); |
| |
| var itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true); |
| this.sidebarTree.appendChild(itemsTreeElement); |
| this.sidebarTree.setFocusable(false); |
| |
| this._sidebarListElement = document.createElement("div"); |
| this.sidebarElement.appendChild(this._sidebarListElement); |
| |
| this._containerContentElement = this.splitView.mainElement; |
| this._containerContentElement.id = "resources-container-content"; |
| |
| this._timelineGrid = new WebInspector.TimelineGrid(); |
| this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement; |
| this._itemsGraphsElement.id = "timeline-graphs"; |
| this._containerContentElement.appendChild(this._timelineGrid.element); |
| this._timelineGrid.gridHeaderElement.id = "timeline-grid-header"; |
| this._memoryStatistics.setMainTimelineGrid(this._timelineGrid); |
| this.element.appendChild(this._timelineGrid.gridHeaderElement); |
| |
| this._topGapElement = document.createElement("div"); |
| this._topGapElement.className = "timeline-gap"; |
| this._itemsGraphsElement.appendChild(this._topGapElement); |
| |
| this._graphRowsElement = document.createElement("div"); |
| this._itemsGraphsElement.appendChild(this._graphRowsElement); |
| |
| this._bottomGapElement = document.createElement("div"); |
| this._bottomGapElement.className = "timeline-gap"; |
| this._itemsGraphsElement.appendChild(this._bottomGapElement); |
| |
| this._expandElements = document.createElement("div"); |
| this._expandElements.id = "orphan-expand-elements"; |
| this._itemsGraphsElement.appendChild(this._expandElements); |
| |
| this._calculator = new WebInspector.TimelineCalculator(this._model); |
| this._createStatusBarItems(); |
| |
| this._frameMode = false; |
| this._boundariesAreValid = true; |
| this._scrollTop = 0; |
| |
| this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this)); |
| this.element.addEventListener("mousemove", this._mouseMove.bind(this), false); |
| this.element.addEventListener("mouseout", this._mouseOut.bind(this), false); |
| |
| |
| this._durationFilter = new WebInspector.TimelineIsLongFilter(); |
| |
| this._expandOffset = 15; |
| |
| this._headerLineCount = 1; |
| this._adjustHeaderHeight(); |
| |
| this._mainThreadTasks = ([]); |
| this._cpuBarsElement = this._timelineGrid.gridHeaderElement.createChild("div", "timeline-cpu-bars"); |
| this._mainThreadMonitoringEnabled = WebInspector.settings.showCpuOnTimelineRuler.get(); |
| WebInspector.settings.showCpuOnTimelineRuler.addChangeListener(this._showCpuOnTimelineRulerChanged, this); |
| |
| this._createFileSelector(); |
| |
| this._model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onTimelineEventRecorded, this); |
| this._model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this); |
| |
| this._registerShortcuts(); |
| |
| this._allRecordsCount = 0; |
| |
| this._presentationModel.addFilter(new WebInspector.TimelineWindowFilter(this._overviewPane)); |
| this._presentationModel.addFilter(new WebInspector.TimelineCategoryFilter()); |
| this._presentationModel.addFilter(this._durationFilter); |
| } |
| |
| |
| WebInspector.TimelinePanel.rowHeight = 18; |
| |
| WebInspector.TimelinePanel.durationFilterPresetsMs = [0, 1, 15]; |
| |
| WebInspector.TimelinePanel.prototype = { |
| _showCpuOnTimelineRulerChanged: function() |
| { |
| var mainThreadMonitoringEnabled = WebInspector.settings.showCpuOnTimelineRuler.get(); |
| if (this._mainThreadMonitoringEnabled !== mainThreadMonitoringEnabled) { |
| this._mainThreadMonitoringEnabled = mainThreadMonitoringEnabled; |
| this._refreshMainThreadBars(); |
| } |
| }, |
| |
| |
| _startSplitterDragging: function(event) |
| { |
| this._dragOffset = this._timelineMemorySplitter.offsetTop + 2 - event.pageY; |
| return true; |
| }, |
| |
| |
| _splitterDragging: function(event) |
| { |
| var top = event.pageY + this._dragOffset |
| this._setSplitterPosition(top); |
| event.preventDefault(); |
| }, |
| |
| |
| _endSplitterDragging: function(event) |
| { |
| delete this._dragOffset; |
| this._memoryStatistics.show(); |
| WebInspector.settings.memoryCounterGraphsHeight.set(this.splitView.element.offsetHeight); |
| }, |
| |
| _setSplitterPosition: function(top) |
| { |
| const overviewHeight = 90; |
| const sectionMinHeight = 100; |
| top = Number.constrain(top, overviewHeight + sectionMinHeight, this.element.offsetHeight - sectionMinHeight); |
| |
| this.splitView.element.style.height = (top - overviewHeight) + "px"; |
| this._timelineMemorySplitter.style.top = (top - 2) + "px"; |
| this._memoryStatistics.setTopPosition(top); |
| this._containerElementHeight = this._containerElement.clientHeight; |
| this.onResize(); |
| }, |
| |
| get calculator() |
| { |
| return this._calculator; |
| }, |
| |
| get statusBarItems() |
| { |
| return this._statusBarItems.select("element").concat([ |
| this._miscStatusBarItems |
| ]); |
| }, |
| |
| defaultFocusedElement: function() |
| { |
| return this.element; |
| }, |
| |
| _createStatusBarItems: function() |
| { |
| this._statusBarItems = ([]); |
| |
| this.toggleTimelineButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record"), "record-profile-status-bar-item"); |
| this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked, this); |
| this._statusBarItems.push(this.toggleTimelineButton); |
| |
| this.clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item"); |
| this.clearButton.addEventListener("click", this._clearPanel, this); |
| this._statusBarItems.push(this.clearButton); |
| |
| this.garbageCollectButton = new WebInspector.StatusBarButton(WebInspector.UIString("Collect Garbage"), "garbage-collect-status-bar-item"); |
| this.garbageCollectButton.addEventListener("click", this._garbageCollectButtonClicked, this); |
| this._statusBarItems.push(this.garbageCollectButton); |
| |
| this._glueParentButton = new WebInspector.StatusBarButton(WebInspector.UIString("Glue asynchronous events to causes"), "glue-async-status-bar-item"); |
| this._glueParentButton.toggled = this._glueRecordsSetting.get(); |
| this._presentationModel.setGlueRecords(this._glueParentButton.toggled); |
| this._glueParentButton.addEventListener("click", this._glueParentButtonClicked, this); |
| this._statusBarItems.push(this._glueParentButton); |
| |
| this._durationFilterSelector = new WebInspector.StatusBarComboBox(this._durationFilterChanged.bind(this)); |
| for (var presetIndex = 0; presetIndex < WebInspector.TimelinePanel.durationFilterPresetsMs.length; ++presetIndex) { |
| var durationMs = WebInspector.TimelinePanel.durationFilterPresetsMs[presetIndex]; |
| var option = document.createElement("option"); |
| if (!durationMs) { |
| option.text = WebInspector.UIString("All"); |
| option.title = WebInspector.UIString("Show all records"); |
| } else { |
| option.text = WebInspector.UIString("\u2265 %dms", durationMs); |
| option.title = WebInspector.UIString("Hide records shorter than %dms", durationMs); |
| } |
| option._durationMs = durationMs; |
| this._durationFilterSelector.addOption(option); |
| this._durationFilterSelector.element.title = this._durationFilterSelector.selectedOption().title; |
| } |
| this._statusBarItems.push(this._durationFilterSelector); |
| |
| this._miscStatusBarItems = document.createElement("div"); |
| this._miscStatusBarItems.className = "status-bar-items timeline-misc-status-bar-items"; |
| |
| this._statusBarFilters = this._miscStatusBarItems.createChild("div", "timeline-misc-status-bar-filters"); |
| var categories = WebInspector.TimelinePresentationModel.categories(); |
| for (var categoryName in categories) { |
| var category = categories[categoryName]; |
| if (category.overviewStripGroupIndex < 0) |
| continue; |
| this._statusBarFilters.appendChild(this._createTimelineCategoryStatusBarCheckbox(category, this._onCategoryCheckboxClicked.bind(this, category))); |
| } |
| |
| var statsContainer = this._statusBarFilters.createChild("div"); |
| statsContainer.className = "timeline-records-stats-container"; |
| |
| this.recordsCounter = statsContainer.createChild("div"); |
| this.recordsCounter.className = "timeline-records-stats"; |
| |
| this.frameStatistics = statsContainer.createChild("div"); |
| this.frameStatistics.className = "timeline-records-stats hidden"; |
| |
| function getAnchor() |
| { |
| return this.frameStatistics; |
| } |
| this._frameStatisticsPopoverHelper = new WebInspector.PopoverHelper(this.frameStatistics, getAnchor.bind(this), this._showFrameStatistics.bind(this)); |
| }, |
| |
| _createTimelineCategoryStatusBarCheckbox: function(category, onCheckboxClicked) |
| { |
| var labelContainer = document.createElement("div"); |
| labelContainer.addStyleClass("timeline-category-statusbar-item"); |
| labelContainer.addStyleClass("timeline-category-" + category.name); |
| labelContainer.addStyleClass("status-bar-item"); |
| |
| var label = document.createElement("label"); |
| var checkElement = document.createElement("input"); |
| checkElement.type = "checkbox"; |
| checkElement.className = "timeline-category-checkbox"; |
| checkElement.checked = true; |
| checkElement.addEventListener("click", onCheckboxClicked, false); |
| label.appendChild(checkElement); |
| |
| var typeElement = document.createElement("span"); |
| typeElement.className = "type"; |
| typeElement.textContent = category.title; |
| label.appendChild(typeElement); |
| |
| labelContainer.appendChild(label); |
| return labelContainer; |
| }, |
| |
| _onCategoryCheckboxClicked: function(category, event) |
| { |
| category.hidden = !event.target.checked; |
| this._invalidateAndScheduleRefresh(true, true); |
| }, |
| |
| |
| _setOperationInProgress: function(indicator) |
| { |
| this._operationInProgress = !!indicator; |
| for (var i = 0; i < this._statusBarItems.length; ++i) |
| this._statusBarItems[i].setEnabled(!this._operationInProgress); |
| this._glueParentButton.setEnabled(!this._operationInProgress && !this._frameController); |
| this._miscStatusBarItems.removeChildren(); |
| this._miscStatusBarItems.appendChild(indicator ? indicator.element : this._statusBarFilters); |
| }, |
| |
| _registerShortcuts: function() |
| { |
| this.registerShortcuts(WebInspector.TimelinePanelDescriptor.ShortcutKeys.StartStopRecording, this._toggleTimelineButtonClicked.bind(this)); |
| this.registerShortcuts(WebInspector.TimelinePanelDescriptor.ShortcutKeys.SaveToFile, this._saveToFile.bind(this)); |
| this.registerShortcuts(WebInspector.TimelinePanelDescriptor.ShortcutKeys.LoadFromFile, this._selectFileToLoad.bind(this)); |
| }, |
| |
| _createFileSelector: function() |
| { |
| if (this._fileSelectorElement) |
| this.element.removeChild(this._fileSelectorElement); |
| |
| this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this)); |
| this.element.appendChild(this._fileSelectorElement); |
| }, |
| |
| _contextMenu: function(event) |
| { |
| var contextMenu = new WebInspector.ContextMenu(event); |
| contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save Timeline data\u2026" : "Save Timeline Data\u2026"), this._saveToFile.bind(this), this._operationInProgress); |
| contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Load Timeline data\u2026" : "Load Timeline Data\u2026"), this._selectFileToLoad.bind(this), this._operationInProgress); |
| contextMenu.show(); |
| }, |
| |
| |
| _saveToFile: function(event) |
| { |
| if (this._operationInProgress) |
| return true; |
| this._model.saveToFile(); |
| return true; |
| }, |
| |
| |
| _selectFileToLoad: function(event) { |
| this._fileSelectorElement.click(); |
| return true; |
| }, |
| |
| |
| _loadFromFile: function(file) |
| { |
| var progressIndicator = this._prepareToLoadTimeline(); |
| if (!progressIndicator) |
| return; |
| this._model.loadFromFile(file, progressIndicator); |
| this._createFileSelector(); |
| }, |
| |
| |
| loadFromURL: function(url) |
| { |
| var progressIndicator = this._prepareToLoadTimeline(); |
| if (!progressIndicator) |
| return; |
| this._model.loadFromURL(url, progressIndicator); |
| }, |
| |
| |
| _prepareToLoadTimeline: function() |
| { |
| if (this._operationInProgress) |
| return null; |
| if (this.toggleTimelineButton.toggled) { |
| this.toggleTimelineButton.toggled = false; |
| this._model.stopRecord(); |
| } |
| var progressIndicator = new WebInspector.ProgressIndicator(); |
| progressIndicator.addEventListener(WebInspector.ProgressIndicator.Events.Done, this._setOperationInProgress.bind(this, null)); |
| this._setOperationInProgress(progressIndicator); |
| return progressIndicator; |
| }, |
| |
| _rootRecord: function() |
| { |
| return this._presentationModel.rootRecord(); |
| }, |
| |
| _updateRecordsCounter: function(recordsInWindowCount) |
| { |
| this.recordsCounter.textContent = WebInspector.UIString("%d of %d records shown", recordsInWindowCount, this._allRecordsCount); |
| }, |
| |
| _updateFrameStatistics: function(frames) |
| { |
| if (frames.length) { |
| this._lastFrameStatistics = new WebInspector.FrameStatistics(frames); |
| var details = WebInspector.UIString("avg: %s, \u03c3: %s", |
| Number.secondsToString(this._lastFrameStatistics.average, true), Number.secondsToString(this._lastFrameStatistics.stddev, true)); |
| } else |
| this._lastFrameStatistics = null; |
| this.frameStatistics.textContent = WebInspector.UIString("%d of %d frames shown", frames.length, this._presentationModel.frames().length); |
| if (details) { |
| this.frameStatistics.appendChild(document.createTextNode(" (")); |
| this.frameStatistics.createChild("span", "timeline-frames-stats").textContent = details; |
| this.frameStatistics.appendChild(document.createTextNode(")")); |
| } |
| }, |
| |
| |
| _showFrameStatistics: function(anchor, popover) |
| { |
| popover.show(WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics(this._lastFrameStatistics), anchor); |
| }, |
| |
| _updateEventDividers: function() |
| { |
| this._timelineGrid.removeEventDividers(); |
| var clientWidth = this._graphRowsElementWidth; |
| var dividers = []; |
| var eventDividerRecords = this._presentationModel.eventDividerRecords(); |
| |
| for (var i = 0; i < eventDividerRecords.length; ++i) { |
| var record = eventDividerRecords[i]; |
| var positions = this._calculator.computeBarGraphWindowPosition(record); |
| var dividerPosition = Math.round(positions.left); |
| if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition]) |
| continue; |
| var divider = WebInspector.TimelinePresentationModel.createEventDivider(record.type, record.title); |
| divider.style.left = dividerPosition + "px"; |
| dividers[dividerPosition] = divider; |
| } |
| this._timelineGrid.addEventDividers(dividers); |
| }, |
| |
| _updateFrameBars: function(frames) |
| { |
| var clientWidth = this._graphRowsElementWidth; |
| if (this._frameContainer) |
| this._frameContainer.removeChildren(); |
| else { |
| const frameContainerBorderWidth = 1; |
| this._frameContainer = document.createElement("div"); |
| this._frameContainer.addStyleClass("fill"); |
| this._frameContainer.addStyleClass("timeline-frame-container"); |
| this._frameContainer.style.height = this._headerLineCount * WebInspector.TimelinePanel.rowHeight + frameContainerBorderWidth + "px"; |
| this._frameContainer.addEventListener("dblclick", this._onFrameDoubleClicked.bind(this), false); |
| } |
| |
| var dividers = [ this._frameContainer ]; |
| |
| for (var i = 0; i < frames.length; ++i) { |
| var frame = frames[i]; |
| var frameStart = this._calculator.computePosition(frame.startTime); |
| var frameEnd = this._calculator.computePosition(frame.endTime); |
| |
| var frameStrip = document.createElement("div"); |
| frameStrip.className = "timeline-frame-strip"; |
| var actualStart = Math.max(frameStart, 0); |
| var width = frameEnd - actualStart; |
| frameStrip.style.left = actualStart + "px"; |
| frameStrip.style.width = width + "px"; |
| frameStrip._frame = frame; |
| |
| const minWidthForFrameInfo = 60; |
| if (width > minWidthForFrameInfo) |
| frameStrip.textContent = Number.secondsToString(frame.endTime - frame.startTime, true); |
| |
| this._frameContainer.appendChild(frameStrip); |
| |
| if (actualStart > 0) { |
| var frameMarker = WebInspector.TimelinePresentationModel.createEventDivider(WebInspector.TimelineModel.RecordType.BeginFrame); |
| frameMarker.style.left = frameStart + "px"; |
| dividers.push(frameMarker); |
| } |
| } |
| this._timelineGrid.addEventDividers(dividers); |
| }, |
| |
| _onFrameDoubleClicked: function(event) |
| { |
| var frameBar = event.target.enclosingNodeOrSelfWithClass("timeline-frame-strip"); |
| if (!frameBar) |
| return; |
| this._overviewPane.zoomToFrame(frameBar._frame); |
| }, |
| |
| _overviewModeChanged: function(event) |
| { |
| var mode = event.data; |
| var shouldShowMemory = mode === WebInspector.TimelineOverviewPane.Mode.Memory; |
| var frameMode = mode === WebInspector.TimelineOverviewPane.Mode.Frames; |
| this._overviewModeSetting.set(mode); |
| if (frameMode !== this._frameMode) { |
| this._frameMode = frameMode; |
| this._glueParentButton.setEnabled(!frameMode); |
| this._presentationModel.setGlueRecords(this._glueParentButton.toggled && !frameMode); |
| this._repopulateRecords(); |
| |
| if (frameMode) { |
| this.element.addStyleClass("timeline-frame-overview"); |
| this.recordsCounter.addStyleClass("hidden"); |
| this.frameStatistics.removeStyleClass("hidden"); |
| this._frameController = new WebInspector.TimelineFrameController(this._model, this._overviewPane, this._presentationModel); |
| } else { |
| this._frameController.dispose(); |
| this._frameController = null; |
| this.element.removeStyleClass("timeline-frame-overview"); |
| this.recordsCounter.removeStyleClass("hidden"); |
| this.frameStatistics.addStyleClass("hidden"); |
| } |
| } |
| if (shouldShowMemory === this._memoryStatistics.visible()) |
| return; |
| if (!shouldShowMemory) { |
| this._timelineMemorySplitter.addStyleClass("hidden"); |
| this._memoryStatistics.hide(); |
| this.splitView.element.style.height = "auto"; |
| this.splitView.element.style.bottom = "0"; |
| this.onResize(); |
| } else { |
| this._timelineMemorySplitter.removeStyleClass("hidden"); |
| this._memoryStatistics.show(); |
| this.splitView.element.style.bottom = "auto"; |
| this._setSplitterPosition(WebInspector.settings.memoryCounterGraphsHeight.get()); |
| } |
| }, |
| |
| |
| _toggleTimelineButtonClicked: function() |
| { |
| if (this._operationInProgress) |
| return true; |
| if (this.toggleTimelineButton.toggled) { |
| this._model.stopRecord(); |
| this.toggleTimelineButton.title = WebInspector.UIString("Record"); |
| } else { |
| this._model.startRecord(this._includeDomCounters, this._includeNativeMemoryStatistics); |
| this.toggleTimelineButton.title = WebInspector.UIString("Stop"); |
| WebInspector.userMetrics.TimelineStarted.record(); |
| } |
| this.toggleTimelineButton.toggled = !this.toggleTimelineButton.toggled; |
| return true; |
| }, |
| |
| _durationFilterChanged: function() |
| { |
| var option = this._durationFilterSelector.selectedOption(); |
| var minimumRecordDuration = +option._durationMs / 1000.0; |
| this._durationFilter.setMinimumRecordDuration(minimumRecordDuration); |
| this._durationFilterSelector.element.title = option.title; |
| this._invalidateAndScheduleRefresh(true, true); |
| }, |
| |
| _garbageCollectButtonClicked: function() |
| { |
| HeapProfilerAgent.collectGarbage(); |
| }, |
| |
| _glueParentButtonClicked: function() |
| { |
| var newValue = !this._glueParentButton.toggled; |
| this._glueParentButton.toggled = newValue; |
| this._presentationModel.setGlueRecords(newValue); |
| this._glueRecordsSetting.set(newValue); |
| this._repopulateRecords(); |
| }, |
| |
| _repopulateRecords: function() |
| { |
| this._resetPanel(); |
| this._automaticallySizeWindow = false; |
| var records = this._model.records; |
| for (var i = 0; i < records.length; ++i) |
| this._innerAddRecordToTimeline(records[i]); |
| this._invalidateAndScheduleRefresh(false, true); |
| }, |
| |
| _onTimelineEventRecorded: function(event) |
| { |
| if (this._innerAddRecordToTimeline(event.data)) |
| this._invalidateAndScheduleRefresh(false, false); |
| }, |
| |
| _innerAddRecordToTimeline: function(record) |
| { |
| if (record.type === WebInspector.TimelineModel.RecordType.Program) { |
| this._mainThreadTasks.push({ |
| startTime: WebInspector.TimelineModel.startTimeInSeconds(record), |
| endTime: WebInspector.TimelineModel.endTimeInSeconds(record) |
| }); |
| } |
| |
| var records = this._presentationModel.addRecord(record); |
| this._allRecordsCount += records.length; |
| var hasVisibleRecords = false; |
| var presentationModel = this._presentationModel; |
| function checkVisible(record) |
| { |
| hasVisibleRecords |= presentationModel.isVisible(record); |
| } |
| WebInspector.TimelinePresentationModel.forAllRecords(records, checkVisible); |
| |
| function isAdoptedRecord(record) |
| { |
| return record.parent !== presentationModel.rootRecord; |
| } |
| |
| return hasVisibleRecords || records.some(isAdoptedRecord); |
| }, |
| |
| sidebarResized: function(event) |
| { |
| var width = event.data; |
| this._resize(width); |
| this._sidebarBackgroundElement.style.width = width + "px"; |
| this._overviewPane.sidebarResized(width); |
| this._memoryStatistics.setSidebarWidth(width); |
| this._timelineGrid.gridHeaderElement.style.left = width + "px"; |
| }, |
| |
| onResize: function() |
| { |
| this._resize(this.splitView.sidebarWidth()); |
| }, |
| |
| |
| _resize: function(sidebarWidth) |
| { |
| this._closeRecordDetails(); |
| this._scheduleRefresh(false, true); |
| this._graphRowsElementWidth = this._graphRowsElement.offsetWidth; |
| this._containerElementHeight = this._containerElement.clientHeight; |
| var lastItemElement = this._statusBarItems[this._statusBarItems.length - 1].element; |
| var minFloatingStatusBarItemsOffset = lastItemElement.totalOffsetLeft() + lastItemElement.offsetWidth; |
| this._timelineGrid.gridHeaderElement.style.width = this._itemsGraphsElement.offsetWidth + "px"; |
| this._miscStatusBarItems.style.left = Math.max(minFloatingStatusBarItemsOffset, sidebarWidth) + "px"; |
| }, |
| |
| _clearPanel: function() |
| { |
| this._model.reset(); |
| }, |
| |
| _onRecordsCleared: function() |
| { |
| this._resetPanel(); |
| this._invalidateAndScheduleRefresh(true, true); |
| }, |
| |
| _resetPanel: function() |
| { |
| this._presentationModel.reset(); |
| this._boundariesAreValid = false; |
| this._adjustScrollPosition(0); |
| this._closeRecordDetails(); |
| this._allRecordsCount = 0; |
| this._automaticallySizeWindow = true; |
| this._mainThreadTasks = []; |
| }, |
| |
| elementsToRestoreScrollPositionsFor: function() |
| { |
| return [this._containerElement]; |
| }, |
| |
| wasShown: function() |
| { |
| WebInspector.Panel.prototype.wasShown.call(this); |
| if (!WebInspector.TimelinePanel._categoryStylesInitialized) { |
| WebInspector.TimelinePanel._categoryStylesInitialized = true; |
| this._injectCategoryStyles(); |
| } |
| this._overviewPane.setMode(this._overviewModeSetting.get()); |
| this._refresh(); |
| }, |
| |
| willHide: function() |
| { |
| this._closeRecordDetails(); |
| WebInspector.Panel.prototype.willHide.call(this); |
| }, |
| |
| _onScroll: function(event) |
| { |
| this._closeRecordDetails(); |
| this._scrollTop = this._containerElement.scrollTop; |
| var dividersTop = Math.max(0, this._scrollTop); |
| this._timelineGrid.setScrollAndDividerTop(this._scrollTop, dividersTop); |
| this._scheduleRefresh(true, true); |
| }, |
| |
| |
| _invalidateAndScheduleRefresh: function(preserveBoundaries, userGesture) |
| { |
| this._presentationModel.invalidateFilteredRecords(); |
| delete this._searchResults; |
| this._scheduleRefresh(preserveBoundaries, userGesture); |
| }, |
| |
| |
| _scheduleRefresh: function(preserveBoundaries, userGesture) |
| { |
| this._closeRecordDetails(); |
| this._boundariesAreValid &= preserveBoundaries; |
| |
| if (!this.isShowing()) |
| return; |
| |
| if (preserveBoundaries || userGesture) |
| this._refresh(); |
| else { |
| if (!this._refreshTimeout) |
| this._refreshTimeout = setTimeout(this._refresh.bind(this), 300); |
| } |
| }, |
| |
| _refresh: function() |
| { |
| if (this._refreshTimeout) { |
| clearTimeout(this._refreshTimeout); |
| delete this._refreshTimeout; |
| } |
| |
| this._timelinePaddingLeft = this._expandOffset; |
| this._calculator.setWindow(this._overviewPane.windowStartTime(), this._overviewPane.windowEndTime()); |
| this._calculator.setDisplayWindow(this._timelinePaddingLeft, this._graphRowsElementWidth); |
| |
| var recordsInWindowCount = this._refreshRecords(); |
| this._updateRecordsCounter(recordsInWindowCount); |
| if (!this._boundariesAreValid) { |
| this._updateEventDividers(); |
| var frames = this._frameController && this._presentationModel.filteredFrames(this._overviewPane.windowStartTime(), this._overviewPane.windowEndTime()); |
| if (frames) { |
| this._updateFrameStatistics(frames); |
| const maxFramesForFrameBars = 30; |
| if (frames.length && frames.length < maxFramesForFrameBars) { |
| this._timelineGrid.removeDividers(); |
| this._updateFrameBars(frames); |
| } else |
| this._timelineGrid.updateDividers(this._calculator); |
| } else |
| this._timelineGrid.updateDividers(this._calculator); |
| if (this._mainThreadMonitoringEnabled) |
| this._refreshMainThreadBars(); |
| } |
| if (this._memoryStatistics.visible()) |
| this._memoryStatistics.refresh(); |
| this._boundariesAreValid = true; |
| }, |
| |
| revealRecordAt: function(time) |
| { |
| var recordToReveal; |
| function findRecordToReveal(record) |
| { |
| if (record.containsTime(time)) { |
| recordToReveal = record; |
| return true; |
| } |
| |
| if (!recordToReveal || record.endTime < time && recordToReveal.endTime < record.endTime) |
| recordToReveal = record; |
| return false; |
| } |
| WebInspector.TimelinePresentationModel.forAllRecords(this._presentationModel.rootRecord().children, null, findRecordToReveal); |
| |
| |
| if (!recordToReveal) { |
| this._containerElement.scrollTop = 0; |
| return; |
| } |
| |
| this._revealRecord(recordToReveal); |
| }, |
| |
| _revealRecord: function(recordToReveal) |
| { |
| |
| this._recordToHighlight = recordToReveal; |
| var treeUpdated = false; |
| for (var parent = recordToReveal.parent; parent !== this._rootRecord(); parent = parent.parent) { |
| treeUpdated = treeUpdated || parent.collapsed; |
| parent.collapsed = false; |
| } |
| if (treeUpdated) |
| this._invalidateAndScheduleRefresh(true, true); |
| |
| var recordsInWindow = this._presentationModel.filteredRecords(); |
| var index = recordsInWindow.indexOf(recordToReveal); |
| this._containerElement.scrollTop = index * WebInspector.TimelinePanel.rowHeight; |
| }, |
| |
| _refreshRecords: function() |
| { |
| var recordsInWindow = this._presentationModel.filteredRecords(); |
| |
| |
| var visibleTop = this._scrollTop; |
| var visibleBottom = visibleTop + this._containerElementHeight; |
| |
| const rowHeight = WebInspector.TimelinePanel.rowHeight; |
| |
| |
| var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - this._headerLineCount, recordsInWindow.length - 1)); |
| var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight)); |
| var lastVisibleLine = Math.max(0, Math.floor(visibleBottom / rowHeight) - this._headerLineCount); |
| if (this._automaticallySizeWindow && recordsInWindow.length > lastVisibleLine) { |
| this._automaticallySizeWindow = false; |
| |
| var windowStartTime = startIndex ? recordsInWindow[startIndex].startTime : this._model.minimumRecordTime(); |
| this._overviewPane.setWindowTimes(windowStartTime, recordsInWindow[Math.max(0, lastVisibleLine - 1)].endTime); |
| recordsInWindow = this._presentationModel.filteredRecords(); |
| endIndex = Math.min(recordsInWindow.length, lastVisibleLine); |
| } |
| |
| |
| this._topGapElement.style.height = (startIndex * rowHeight) + "px"; |
| this.sidebarTreeElement.style.height = ((startIndex + this._headerLineCount) * rowHeight) + "px"; |
| this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px"; |
| |
| |
| var listRowElement = this._sidebarListElement.firstChild; |
| var width = this._graphRowsElementWidth; |
| this._itemsGraphsElement.removeChild(this._graphRowsElement); |
| var graphRowElement = this._graphRowsElement.firstChild; |
| var scheduleRefreshCallback = this._invalidateAndScheduleRefresh.bind(this, true, true); |
| this._itemsGraphsElement.removeChild(this._expandElements); |
| this._expandElements.removeChildren(); |
| |
| this._clearRecordHighlight(); |
| var highlightedRecord = this._recordToHighlight; |
| delete this._recordToHighlight; |
| |
| for (var i = 0; i < endIndex; ++i) { |
| var record = recordsInWindow[i]; |
| var isEven = !(i % 2); |
| |
| if (i < startIndex) { |
| var lastChildIndex = i + record.visibleChildrenCount; |
| if (lastChildIndex >= startIndex && lastChildIndex < endIndex) { |
| var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements); |
| var positions = this._calculator.computeBarGraphWindowPosition(record); |
| expandElement._update(record, i, positions.left - this._expandOffset, positions.width); |
| } |
| } else { |
| if (!listRowElement) { |
| listRowElement = new WebInspector.TimelineRecordListRow().element; |
| this._sidebarListElement.appendChild(listRowElement); |
| } |
| if (!graphRowElement) { |
| graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, scheduleRefreshCallback).element; |
| this._graphRowsElement.appendChild(graphRowElement); |
| } |
| |
| if (highlightedRecord === record) { |
| this._highlightedListRowElement = listRowElement; |
| this._highlightedGraphRowElement = graphRowElement; |
| } |
| |
| listRowElement.row.update(record, isEven, visibleTop); |
| graphRowElement.row.update(record, isEven, this._calculator, this._expandOffset, i); |
| |
| listRowElement = listRowElement.nextSibling; |
| graphRowElement = graphRowElement.nextSibling; |
| } |
| } |
| |
| |
| while (listRowElement) { |
| var nextElement = listRowElement.nextSibling; |
| listRowElement.row.dispose(); |
| listRowElement = nextElement; |
| } |
| while (graphRowElement) { |
| var nextElement = graphRowElement.nextSibling; |
| graphRowElement.row.dispose(); |
| graphRowElement = nextElement; |
| } |
| |
| this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement); |
| this._itemsGraphsElement.appendChild(this._expandElements); |
| this._adjustScrollPosition((recordsInWindow.length + this._headerLineCount) * rowHeight); |
| this._updateSearchHighlight(false); |
| |
| if (this._highlightedListRowElement) { |
| this._highlightedListRowElement.addStyleClass("highlighted-timeline-record"); |
| this._highlightedGraphRowElement.addStyleClass("highlighted-timeline-record"); |
| } |
| |
| return recordsInWindow.length; |
| }, |
| |
| _clearRecordHighlight: function() |
| { |
| if (!this._highlightedListRowElement) |
| return; |
| this._highlightedListRowElement.removeStyleClass("highlighted-timeline-record"); |
| delete this._highlightedListRowElement; |
| this._highlightedGraphRowElement.removeStyleClass("highlighted-timeline-record"); |
| delete this._highlightedGraphRowElement; |
| }, |
| |
| _refreshMainThreadBars: function() |
| { |
| const barOffset = 3; |
| const minGap = 3; |
| |
| var minWidth = WebInspector.TimelineCalculator._minWidth; |
| var widthAdjustment = minWidth / 2; |
| |
| var width = this._graphRowsElementWidth; |
| var boundarySpan = this._overviewPane.windowEndTime() - this._overviewPane.windowStartTime(); |
| var scale = boundarySpan / (width - minWidth - this._timelinePaddingLeft); |
| var startTime = this._overviewPane.windowStartTime() - this._timelinePaddingLeft * scale; |
| var endTime = startTime + width * scale; |
| |
| var tasks = this._mainThreadMonitoringEnabled ? this._mainThreadTasks : []; |
| |
| function compareEndTime(value, task) |
| { |
| return value < task.endTime ? -1 : 1; |
| } |
| |
| var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, tasks, compareEndTime); |
| |
| var container = this._cpuBarsElement; |
| var element = container.firstChild; |
| var lastElement; |
| var lastLeft; |
| var lastRight; |
| |
| for (; taskIndex < tasks.length; ++taskIndex) { |
| var task = tasks[taskIndex]; |
| if (task.startTime > endTime) |
| break; |
| |
| var left = Math.max(0, this._calculator.computePosition(task.startTime) + barOffset - widthAdjustment); |
| var right = Math.min(width, this._calculator.computePosition(task.endTime) + barOffset + widthAdjustment); |
| |
| if (lastElement) { |
| var gap = Math.floor(left) - Math.ceil(lastRight); |
| if (gap < minGap) { |
| lastRight = right; |
| lastElement._tasksInfo.lastTaskIndex = taskIndex; |
| continue; |
| } |
| lastElement.style.width = (lastRight - lastLeft) + "px"; |
| } |
| |
| if (!element) |
| element = container.createChild("div", "timeline-graph-bar"); |
| |
| element.style.left = left + "px"; |
| element._tasksInfo = {tasks: tasks, firstTaskIndex: taskIndex, lastTaskIndex: taskIndex}; |
| lastLeft = left; |
| lastRight = right; |
| |
| lastElement = element; |
| element = element.nextSibling; |
| } |
| |
| if (lastElement) |
| lastElement.style.width = (lastRight - lastLeft) + "px"; |
| |
| while (element) { |
| var nextElement = element.nextSibling; |
| element._tasksInfo = null; |
| container.removeChild(element); |
| element = nextElement; |
| } |
| }, |
| |
| _adjustHeaderHeight: function() |
| { |
| const headerBorderWidth = 1; |
| const headerMargin = 2; |
| |
| var headerHeight = this._headerLineCount * WebInspector.TimelinePanel.rowHeight; |
| this.sidebarElement.firstChild.style.height = headerHeight + "px"; |
| this._timelineGrid.dividersLabelBarElement.style.height = headerHeight + headerMargin + "px"; |
| this._itemsGraphsElement.style.top = headerHeight + headerBorderWidth + "px"; |
| }, |
| |
| _adjustScrollPosition: function(totalHeight) |
| { |
| |
| if ((this._scrollTop + this._containerElementHeight) > totalHeight + 1) |
| this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight); |
| }, |
| |
| _getPopoverAnchor: function(element) |
| { |
| return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") || |
| element.enclosingNodeOrSelfWithClass("timeline-tree-item") || |
| element.enclosingNodeOrSelfWithClass("timeline-frame-strip"); |
| }, |
| |
| _mouseOut: function(e) |
| { |
| this._hideQuadHighlight(); |
| }, |
| |
| |
| _mouseMove: function(e) |
| { |
| var anchor = this._getPopoverAnchor(e.target); |
| |
| if (anchor && anchor.row && anchor.row._record.highlightQuad) |
| this._highlightQuad(anchor.row._record.highlightQuad); |
| else |
| this._hideQuadHighlight(); |
| |
| if (anchor && anchor._tasksInfo) { |
| var offset = anchor.offsetLeft; |
| this._timelineGrid.showCurtains(offset >= 0 ? offset : 0, anchor.offsetWidth); |
| } else |
| this._timelineGrid.hideCurtains(); |
| }, |
| |
| |
| _highlightQuad: function(quad) |
| { |
| if (this._highlightedQuad === quad) |
| return; |
| this._highlightedQuad = quad; |
| DOMAgent.highlightQuad(quad, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA()); |
| }, |
| |
| _hideQuadHighlight: function() |
| { |
| if (this._highlightedQuad) { |
| delete this._highlightedQuad; |
| DOMAgent.hideHighlight(); |
| } |
| }, |
| |
| |
| _showPopover: function(anchor, popover) |
| { |
| if (anchor.hasStyleClass("timeline-frame-strip")) { |
| var frame = anchor._frame; |
| popover.show(WebInspector.TimelinePresentationModel.generatePopupContentForFrame(frame), anchor); |
| } else { |
| if (anchor.row && anchor.row._record) |
| anchor.row._record.generatePopupContent(showCallback); |
| else if (anchor._tasksInfo) |
| popover.show(this._presentationModel.generateMainThreadBarPopupContent(anchor._tasksInfo), anchor, null, null, WebInspector.Popover.Orientation.Bottom); |
| } |
| |
| function showCallback(popupContent) |
| { |
| popover.show(popupContent, anchor); |
| } |
| }, |
| |
| _closeRecordDetails: function() |
| { |
| this._popoverHelper.hidePopover(); |
| }, |
| |
| _injectCategoryStyles: function() |
| { |
| var style = document.createElement("style"); |
| var categories = WebInspector.TimelinePresentationModel.categories(); |
| |
| style.textContent = Object.values(categories).map(WebInspector.TimelinePresentationModel.createStyleRuleForCategory).join("\n"); |
| document.head.appendChild(style); |
| }, |
| |
| jumpToNextSearchResult: function() |
| { |
| this._jumpToAdjacentRecord(1); |
| }, |
| |
| jumpToPreviousSearchResult: function() |
| { |
| this._jumpToAdjacentRecord(-1); |
| }, |
| |
| _jumpToAdjacentRecord: function(offset) |
| { |
| if (!this._searchResults || !this._searchResults.length || !this._selectedSearchResult) |
| return; |
| var index = this._searchResults.indexOf(this._selectedSearchResult); |
| index = (index + offset + this._searchResults.length) % this._searchResults.length; |
| this._selectSearchResult(index); |
| this._highlightSelectedSearchResult(true); |
| }, |
| |
| _selectSearchResult: function(index) |
| { |
| this._selectedSearchResult = this._searchResults[index]; |
| WebInspector.searchController.updateCurrentMatchIndex(index, this); |
| }, |
| |
| _highlightSelectedSearchResult: function(revealRecord) |
| { |
| this._clearHighlight(); |
| if (this._searchFilter) |
| return; |
| |
| var record = this._selectedSearchResult; |
| if (!record) |
| return; |
| |
| for (var element = this._sidebarListElement.firstChild; element; element = element.nextSibling) { |
| if (element.row._record === record) { |
| element.row.highlight(this._searchRegExp, this._highlightDomChanges); |
| return; |
| } |
| } |
| |
| if (revealRecord) |
| this._revealRecord(record); |
| }, |
| |
| _clearHighlight: function() |
| { |
| if (this._highlightDomChanges) |
| WebInspector.revertDomChanges(this._highlightDomChanges); |
| this._highlightDomChanges = []; |
| }, |
| |
| |
| _updateSearchHighlight: function(revealRecord) |
| { |
| if (this._searchFilter || !this._searchRegExp) { |
| this._clearHighlight(); |
| return; |
| } |
| |
| if (!this._searchResults) |
| this._updateSearchResults(); |
| |
| this._highlightSelectedSearchResult(revealRecord); |
| }, |
| |
| _updateSearchResults: function() |
| { |
| var searchRegExp = this._searchRegExp; |
| if (!searchRegExp) |
| return; |
| |
| var matches = []; |
| var presentationModel = this._presentationModel; |
| |
| function processRecord(record) |
| { |
| if (presentationModel.isVisible(record) && WebInspector.TimelineRecordListRow.testContentMatching(record, searchRegExp)) |
| matches.push(record); |
| return false; |
| } |
| WebInspector.TimelinePresentationModel.forAllRecords(presentationModel.rootRecord().children, processRecord); |
| |
| var matchesCount = matches.length; |
| if (matchesCount) { |
| this._searchResults = matches; |
| WebInspector.searchController.updateSearchMatchesCount(matchesCount, this); |
| |
| var selectedIndex = matches.indexOf(this._selectedSearchResult); |
| if (selectedIndex === -1) |
| selectedIndex = 0; |
| this._selectSearchResult(selectedIndex); |
| } else { |
| WebInspector.searchController.updateSearchMatchesCount(0, this); |
| delete this._selectedSearchResult; |
| } |
| }, |
| |
| searchCanceled: function() |
| { |
| this._clearHighlight(); |
| delete this._searchResults; |
| delete this._selectedSearchResult; |
| delete this._searchRegExp; |
| }, |
| |
| |
| canFilter: function() |
| { |
| return true; |
| }, |
| |
| performFilter: function(searchQuery) |
| { |
| this._presentationModel.removeFilter(this._searchFilter); |
| delete this._searchFilter; |
| this.searchCanceled(); |
| if (searchQuery) { |
| this._searchFilter = new WebInspector.TimelineSearchFilter(createPlainTextSearchRegex(searchQuery, "i")); |
| this._presentationModel.addFilter(this._searchFilter); |
| } |
| this._invalidateAndScheduleRefresh(true, true); |
| }, |
| |
| performSearch: function(searchQuery) |
| { |
| this._searchRegExp = createPlainTextSearchRegex(searchQuery, "i"); |
| delete this._searchResults; |
| this._updateSearchHighlight(true); |
| }, |
| |
| __proto__: WebInspector.Panel.prototype |
| } |
| |
| |
| WebInspector.TimelineCalculator = function(model) |
| { |
| this._model = model; |
| } |
| |
| WebInspector.TimelineCalculator._minWidth = 5; |
| |
| WebInspector.TimelineCalculator.prototype = { |
| |
| computePosition: function(time) |
| { |
| return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this.paddingLeft; |
| }, |
| |
| computeBarGraphPercentages: function(record) |
| { |
| var start = (record.startTime - this._minimumBoundary) / this.boundarySpan() * 100; |
| var end = (record.startTime + record.selfTime - this._minimumBoundary) / this.boundarySpan() * 100; |
| var endWithChildren = (record.lastChildEndTime - this._minimumBoundary) / this.boundarySpan() * 100; |
| var cpuWidth = record.coalesced ? endWithChildren - start : record.cpuTime / this.boundarySpan() * 100; |
| return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth}; |
| }, |
| |
| computeBarGraphWindowPosition: function(record) |
| { |
| var percentages = this.computeBarGraphPercentages(record); |
| var widthAdjustment = 0; |
| |
| var left = this.computePosition(record.startTime); |
| var width = (percentages.end - percentages.start) / 100 * this._workingArea; |
| if (width < WebInspector.TimelineCalculator._minWidth) { |
| widthAdjustment = WebInspector.TimelineCalculator._minWidth - width; |
| left -= widthAdjustment / 2; |
| width += widthAdjustment; |
| } |
| var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * this._workingArea + widthAdjustment; |
| var cpuWidth = percentages.cpuWidth / 100 * this._workingArea + widthAdjustment; |
| if (percentages.endWithChildren > percentages.end) |
| widthWithChildren += widthAdjustment; |
| return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth}; |
| }, |
| |
| setWindow: function(minimumBoundary, maximumBoundary) |
| { |
| this._minimumBoundary = minimumBoundary; |
| this._maximumBoundary = maximumBoundary; |
| }, |
| |
| |
| setDisplayWindow: function(paddingLeft, clientWidth) |
| { |
| this._workingArea = clientWidth - WebInspector.TimelineCalculator._minWidth - paddingLeft; |
| this.paddingLeft = paddingLeft; |
| }, |
| |
| formatTime: function(value) |
| { |
| return Number.secondsToString(value + this._minimumBoundary - this._model.minimumRecordTime()); |
| }, |
| |
| maximumBoundary: function() |
| { |
| return this._maximumBoundary; |
| }, |
| |
| minimumBoundary: function() |
| { |
| return this._minimumBoundary; |
| }, |
| |
| zeroTime: function() |
| { |
| return this._model.minimumRecordTime(); |
| }, |
| |
| boundarySpan: function() |
| { |
| return this._maximumBoundary - this._minimumBoundary; |
| } |
| } |
| |
| |
| WebInspector.TimelineRecordListRow = function() |
| { |
| this.element = document.createElement("div"); |
| this.element.row = this; |
| this.element.style.cursor = "pointer"; |
| var iconElement = document.createElement("span"); |
| iconElement.className = "timeline-tree-icon"; |
| this.element.appendChild(iconElement); |
| |
| this._typeElement = document.createElement("span"); |
| this._typeElement.className = "type"; |
| this.element.appendChild(this._typeElement); |
| |
| var separatorElement = document.createElement("span"); |
| separatorElement.className = "separator"; |
| separatorElement.textContent = " "; |
| |
| this._dataElement = document.createElement("span"); |
| this._dataElement.className = "data dimmed"; |
| |
| this.element.appendChild(separatorElement); |
| this.element.appendChild(this._dataElement); |
| } |
| |
| WebInspector.TimelineRecordListRow.prototype = { |
| update: function(record, isEven, offset) |
| { |
| this._record = record; |
| this._offset = offset; |
| |
| this.element.className = "timeline-tree-item timeline-category-" + record.category.name; |
| if (isEven) |
| this.element.addStyleClass("even"); |
| if (record.hasWarning) |
| this.element.addStyleClass("warning"); |
| else if (record.childHasWarning) |
| this.element.addStyleClass("child-warning"); |
| if (record.isBackground) |
| this.element.addStyleClass("background"); |
| |
| this._typeElement.textContent = record.title; |
| |
| if (this._dataElement.firstChild) |
| this._dataElement.removeChildren(); |
| |
| if (record.detailsNode()) |
| this._dataElement.appendChild(record.detailsNode()); |
| }, |
| |
| highlight: function(regExp, domChanges) |
| { |
| var matchInfo = this.element.textContent.match(regExp); |
| if (matchInfo) |
| WebInspector.highlightSearchResult(this.element, matchInfo.index, matchInfo[0].length, domChanges); |
| }, |
| |
| dispose: function() |
| { |
| this.element.parentElement.removeChild(this.element); |
| } |
| } |
| |
| |
| WebInspector.TimelineRecordListRow.testContentMatching = function(record, regExp) |
| { |
| var toSearchText = record.title; |
| if (record.detailsNode()) |
| toSearchText += " " + record.detailsNode().textContent; |
| return regExp.test(toSearchText); |
| } |
| |
| |
| WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh) |
| { |
| this.element = document.createElement("div"); |
| this.element.row = this; |
| |
| this._barAreaElement = document.createElement("div"); |
| this._barAreaElement.className = "timeline-graph-bar-area"; |
| this.element.appendChild(this._barAreaElement); |
| |
| this._barWithChildrenElement = document.createElement("div"); |
| this._barWithChildrenElement.className = "timeline-graph-bar with-children"; |
| this._barWithChildrenElement.row = this; |
| this._barAreaElement.appendChild(this._barWithChildrenElement); |
| |
| this._barCpuElement = document.createElement("div"); |
| this._barCpuElement.className = "timeline-graph-bar cpu" |
| this._barCpuElement.row = this; |
| this._barAreaElement.appendChild(this._barCpuElement); |
| |
| this._barElement = document.createElement("div"); |
| this._barElement.className = "timeline-graph-bar"; |
| this._barElement.row = this; |
| this._barAreaElement.appendChild(this._barElement); |
| |
| this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer); |
| this._expandElement._element.addEventListener("click", this._onClick.bind(this)); |
| |
| this._scheduleRefresh = scheduleRefresh; |
| } |
| |
| WebInspector.TimelineRecordGraphRow.prototype = { |
| update: function(record, isEven, calculator, expandOffset, index) |
| { |
| this._record = record; |
| this.element.className = "timeline-graph-side timeline-category-" + record.category.name; |
| if (isEven) |
| this.element.addStyleClass("even"); |
| if (record.isBackground) |
| this.element.addStyleClass("background"); |
| |
| var barPosition = calculator.computeBarGraphWindowPosition(record); |
| this._barWithChildrenElement.style.left = barPosition.left + "px"; |
| this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px"; |
| this._barElement.style.left = barPosition.left + "px"; |
| this._barElement.style.width = barPosition.width + "px"; |
| this._barCpuElement.style.left = barPosition.left + "px"; |
| this._barCpuElement.style.width = barPosition.cpuWidth + "px"; |
| this._expandElement._update(record, index, barPosition.left - expandOffset, barPosition.width); |
| }, |
| |
| _onClick: function(event) |
| { |
| this._record.collapsed = !this._record.collapsed; |
| this._scheduleRefresh(false, true); |
| }, |
| |
| dispose: function() |
| { |
| this.element.parentElement.removeChild(this.element); |
| this._expandElement._dispose(); |
| } |
| } |
| |
| |
| WebInspector.TimelineExpandableElement = function(container) |
| { |
| this._element = document.createElement("div"); |
| this._element.className = "timeline-expandable"; |
| |
| var leftBorder = document.createElement("div"); |
| leftBorder.className = "timeline-expandable-left"; |
| this._element.appendChild(leftBorder); |
| |
| container.appendChild(this._element); |
| } |
| |
| WebInspector.TimelineExpandableElement.prototype = { |
| _update: function(record, index, left, width) |
| { |
| const rowHeight = WebInspector.TimelinePanel.rowHeight; |
| if (record.visibleChildrenCount || record.invisibleChildrenCount) { |
| this._element.style.top = index * rowHeight + "px"; |
| this._element.style.left = left + "px"; |
| this._element.style.width = Math.max(12, width + 25) + "px"; |
| if (!record.collapsed) { |
| this._element.style.height = (record.visibleChildrenCount + 1) * rowHeight + "px"; |
| this._element.addStyleClass("timeline-expandable-expanded"); |
| this._element.removeStyleClass("timeline-expandable-collapsed"); |
| } else { |
| this._element.style.height = rowHeight + "px"; |
| this._element.addStyleClass("timeline-expandable-collapsed"); |
| this._element.removeStyleClass("timeline-expandable-expanded"); |
| } |
| this._element.removeStyleClass("hidden"); |
| } else |
| this._element.addStyleClass("hidden"); |
| }, |
| |
| _dispose: function() |
| { |
| this._element.parentElement.removeChild(this._element); |
| } |
| } |
| |
| |
| WebInspector.TimelineCategoryFilter = function() |
| { |
| } |
| |
| WebInspector.TimelineCategoryFilter.prototype = { |
| |
| accept: function(record) |
| { |
| return !record.category.hidden && record.type !== WebInspector.TimelineModel.RecordType.BeginFrame; |
| } |
| } |
| |
| |
| WebInspector.TimelineIsLongFilter = function() |
| { |
| this._minimumRecordDuration = 0; |
| } |
| |
| WebInspector.TimelineIsLongFilter.prototype = { |
| |
| setMinimumRecordDuration: function(value) |
| { |
| this._minimumRecordDuration = value; |
| }, |
| |
| |
| accept: function(record) |
| { |
| return this._minimumRecordDuration ? ((record.lastChildEndTime - record.startTime) >= this._minimumRecordDuration) : true; |
| } |
| } |
| |
| |
| WebInspector.TimelineSearchFilter = function(regExp) |
| { |
| this._regExp = regExp; |
| } |
| |
| WebInspector.TimelineSearchFilter.prototype = { |
| |
| |
| accept: function(record) |
| { |
| return WebInspector.TimelineRecordListRow.testContentMatching(record, this._regExp); |
| } |
| } |