diff options
Diffstat (limited to 'Source/WebCore/inspector/front-end/TimelineOverviewPane.js')
-rw-r--r-- | Source/WebCore/inspector/front-end/TimelineOverviewPane.js | 600 |
1 files changed, 600 insertions, 0 deletions
diff --git a/Source/WebCore/inspector/front-end/TimelineOverviewPane.js b/Source/WebCore/inspector/front-end/TimelineOverviewPane.js new file mode 100644 index 0000000..55e24c5 --- /dev/null +++ b/Source/WebCore/inspector/front-end/TimelineOverviewPane.js @@ -0,0 +1,600 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.TimelineOverviewPane = function(categories) +{ + this._categories = categories; + + this.statusBarFilters = document.createElement("div"); + this.statusBarFilters.className = "status-bar-items"; + for (var categoryName in this._categories) { + var category = this._categories[categoryName]; + this.statusBarFilters.appendChild(this._createTimelineCategoryStatusBarCheckbox(category, this._onCheckboxClicked.bind(this, category))); + } + + this._overviewGrid = new WebInspector.TimelineGrid(); + this._overviewGrid.element.id = "timeline-overview-grid"; + this._overviewGrid.itemsGraphsElement.id = "timeline-overview-timelines"; + this._overviewGrid.element.addEventListener("mousedown", this._dragWindow.bind(this), true); + + this._heapGraph = new WebInspector.HeapGraph(); + this._heapGraph.element.id = "timeline-overview-memory"; + this._overviewGrid.element.insertBefore(this._heapGraph.element, this._overviewGrid.itemsGraphsElement); + + this.element = this._overviewGrid.element; + + this._categoryGraphs = {}; + var i = 0; + for (var category in this._categories) { + var categoryGraph = new WebInspector.TimelineCategoryGraph(this._categories[category], i++ % 2); + this._categoryGraphs[category] = categoryGraph; + this._overviewGrid.itemsGraphsElement.appendChild(categoryGraph.graphElement); + } + this._overviewGrid.setScrollAndDividerTop(0, 0); + + this._overviewWindowElement = document.createElement("div"); + this._overviewWindowElement.id = "timeline-overview-window"; + this._overviewGrid.element.appendChild(this._overviewWindowElement); + + this._overviewWindowBordersElement = document.createElement("div"); + this._overviewWindowBordersElement.className = "timeline-overview-window-rulers"; + this._overviewGrid.element.appendChild(this._overviewWindowBordersElement); + + var overviewDividersBackground = document.createElement("div"); + overviewDividersBackground.className = "timeline-overview-dividers-background"; + this._overviewGrid.element.appendChild(overviewDividersBackground); + + this._leftResizeElement = document.createElement("div"); + this._leftResizeElement.className = "timeline-window-resizer"; + this._leftResizeElement.style.left = 0; + this._overviewGrid.element.appendChild(this._leftResizeElement); + + this._rightResizeElement = document.createElement("div"); + this._rightResizeElement.className = "timeline-window-resizer timeline-window-resizer-right"; + this._rightResizeElement.style.right = 0; + this._overviewGrid.element.appendChild(this._rightResizeElement); + + this._overviewCalculator = new WebInspector.TimelineOverviewCalculator(); + + this.windowLeft = 0.0; + this.windowRight = 1.0; +} + +WebInspector.TimelineOverviewPane.minSelectableSize = 12; + +WebInspector.TimelineOverviewPane.prototype = { + showTimelines: function(event) { + this._heapGraph.hide(); + this._overviewGrid.itemsGraphsElement.removeStyleClass("hidden"); + }, + + showMemoryGraph: function(records) { + this._heapGraph.show(); + this._heapGraph.update(records); + this._overviewGrid.itemsGraphsElement.addStyleClass("hidden"); + }, + + _onCheckboxClicked: function (category, event) { + if (event.target.checked) + category.hidden = false; + else + category.hidden = true; + this._categoryGraphs[category.name].dimmed = !event.target.checked; + this.dispatchEventToListeners("filter changed"); + }, + + _forAllRecords: function(recordsArray, callback) + { + if (!recordsArray) + return; + for (var i = 0; i < recordsArray.length; ++i) { + callback(recordsArray[i]); + this._forAllRecords(recordsArray[i].children, callback); + } + }, + + update: function(records, showShortEvents) + { + this._showShortEvents = showShortEvents; + // Clear summary bars. + var timelines = {}; + for (var category in this._categories) { + timelines[category] = []; + this._categoryGraphs[category].clearChunks(); + } + + // Create sparse arrays with 101 cells each to fill with chunks for a given category. + this._overviewCalculator.reset(); + this._forAllRecords(records, this._overviewCalculator.updateBoundaries.bind(this._overviewCalculator)); + + function markTimeline(record) + { + if (!(this._showShortEvents || record.isLong())) + return; + var percentages = this._overviewCalculator.computeBarGraphPercentages(record); + + var end = Math.round(percentages.end); + var categoryName = record.category.name; + for (var j = Math.round(percentages.start); j <= end; ++j) + timelines[categoryName][j] = true; + } + this._forAllRecords(records, markTimeline.bind(this)); + + // Convert sparse arrays to continuous segments, render graphs for each. + for (var category in this._categories) { + var timeline = timelines[category]; + window.timelineSaved = timeline; + var chunkStart = -1; + for (var j = 0; j < 101; ++j) { + if (timeline[j]) { + if (chunkStart === -1) + chunkStart = j; + } else { + if (chunkStart !== -1) { + this._categoryGraphs[category].addChunk(chunkStart, j); + chunkStart = -1; + } + } + } + if (chunkStart !== -1) { + this._categoryGraphs[category].addChunk(chunkStart, 100); + chunkStart = -1; + } + } + + this._heapGraph.setSize(this._overviewGrid.element.offsetWidth, 60); + if (this._heapGraph.visible) + this._heapGraph.update(records); + + this._overviewGrid.updateDividers(true, this._overviewCalculator); + }, + + updateEventDividers: function(records, dividerConstructor) + { + 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 = dividerConstructor(record); + divider.style.left = positions.start + "%"; + dividers[dividerPosition] = divider; + } + this._overviewGrid.addEventDividers(dividers); + }, + + updateMainViewWidth: function(width, records) + { + this._overviewGrid.element.style.left = width + "px"; + this.statusBarFilters.style.left = Math.max(155, width) + "px"; + }, + + reset: function() + { + this.windowLeft = 0.0; + this.windowRight = 1.0; + this._overviewWindowElement.style.left = "0%"; + this._overviewWindowElement.style.width = "100%"; + this._overviewWindowBordersElement.style.left = "0%"; + this._overviewWindowBordersElement.style.right = "0%"; + this._leftResizeElement.style.left = "0%"; + this._rightResizeElement.style.left = "100%"; + this._overviewCalculator.reset(); + this._overviewGrid.updateDividers(true, this._overviewCalculator); + }, + + _resizeWindow: function(resizeElement, event) + { + WebInspector.elementDragStart(resizeElement, this._windowResizeDragging.bind(this, resizeElement), this._endWindowDragging.bind(this), event, "col-resize"); + }, + + _windowResizeDragging: function(resizeElement, event) + { + if (resizeElement === this._leftResizeElement) + this._resizeWindowLeft(event.pageX - this._overviewGrid.element.offsetLeft); + else + this._resizeWindowRight(event.pageX - this._overviewGrid.element.offsetLeft); + event.preventDefault(); + }, + + _dragWindow: function(event) + { + var node = event.target; + while (node) { + if (node === this._overviewGrid._dividersLabelBarElement) { + WebInspector.elementDragStart(this._overviewWindowElement, this._windowDragging.bind(this, event.pageX, + this._leftResizeElement.offsetLeft, this._rightResizeElement.offsetLeft), this._endWindowDragging.bind(this), event, "ew-resize"); + break; + } else if (node === this._overviewGrid.element) { + var position = event.pageX - this._overviewGrid.element.offsetLeft; + this._overviewWindowSelector = new WebInspector.TimelinePanel.WindowSelector(this._overviewGrid.element, position, event); + WebInspector.elementDragStart(null, this._windowSelectorDragging.bind(this), this._endWindowSelectorDragging.bind(this), event, "col-resize"); + break; + } else if (node === this._leftResizeElement || node === this._rightResizeElement) { + this._resizeWindow(node, event); + break; + } + node = node.parentNode; + } + }, + + _windowSelectorDragging: function(event) + { + this._overviewWindowSelector._updatePosition(event.pageX - this._overviewGrid.element.offsetLeft); + event.preventDefault(); + }, + + _endWindowSelectorDragging: function(event) + { + WebInspector.elementDragEnd(event); + var window = this._overviewWindowSelector._close(event.pageX - this._overviewGrid.element.offsetLeft); + delete this._overviewWindowSelector; + if (window.end - window.start < WebInspector.TimelineOverviewPane.minSelectableSize) + if (this._overviewGrid.itemsGraphsElement.offsetWidth - window.end > WebInspector.TimelineOverviewPane.minSelectableSize) + window.end = window.start + WebInspector.TimelineOverviewPane.minSelectableSize; + else + window.start = window.end - WebInspector.TimelineOverviewPane.minSelectableSize; + this._setWindowPosition(window.start, window.end); + }, + + _windowDragging: function(startX, windowLeft, windowRight, event) + { + var delta = event.pageX - startX; + var start = windowLeft + delta; + var end = windowRight + delta; + var windowSize = windowRight - windowLeft; + + if (start < 0) { + start = 0; + end = windowSize; + } + + if (end > this._overviewGrid.element.clientWidth) { + end = this._overviewGrid.element.clientWidth; + start = end - windowSize; + } + this._setWindowPosition(start, end); + + event.preventDefault(); + }, + + _resizeWindowLeft: function(start) + { + // Glue to edge. + if (start < 10) + start = 0; + else if (start > this._rightResizeElement.offsetLeft - 4) + start = this._rightResizeElement.offsetLeft - 4; + this._setWindowPosition(start, null); + }, + + _resizeWindowRight: function(end) + { + // Glue to edge. + if (end > this._overviewGrid.element.clientWidth - 10) + end = this._overviewGrid.element.clientWidth; + else if (end < this._leftResizeElement.offsetLeft + WebInspector.TimelineOverviewPane.minSelectableSize) + end = this._leftResizeElement.offsetLeft + WebInspector.TimelineOverviewPane.minSelectableSize; + this._setWindowPosition(null, end); + }, + + _setWindowPosition: function(start, end) + { + const rulerAdjustment = 1 / this._overviewGrid.element.clientWidth; + if (typeof start === "number") { + this.windowLeft = start / this._overviewGrid.element.clientWidth; + this._leftResizeElement.style.left = this.windowLeft * 100 + "%"; + this._overviewWindowElement.style.left = this.windowLeft * 100 + "%"; + this._overviewWindowBordersElement.style.left = (this.windowLeft - rulerAdjustment) * 100 + "%"; + } + if (typeof end === "number") { + this.windowRight = end / this._overviewGrid.element.clientWidth; + this._rightResizeElement.style.left = this.windowRight * 100 + "%"; + } + this._overviewWindowElement.style.width = (this.windowRight - this.windowLeft) * 100 + "%"; + this._overviewWindowBordersElement.style.right = (1 - this.windowRight + 2 * rulerAdjustment) * 100 + "%"; + this.dispatchEventToListeners("window changed"); + }, + + _endWindowDragging: function(event) + { + WebInspector.elementDragEnd(event); + }, + + _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); + label.appendChild(checkElement); + + var typeElement = document.createElement("span"); + typeElement.className = "type"; + typeElement.textContent = category.title; + label.appendChild(typeElement); + + labelContainer.appendChild(label); + return labelContainer; + } + +} + +WebInspector.TimelineOverviewPane.prototype.__proto__ = WebInspector.Object.prototype; + + +WebInspector.TimelineOverviewCalculator = function() +{ +} + +WebInspector.TimelineOverviewCalculator.prototype = { + computeBarGraphPercentages: function(record) + { + var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100; + var end = (record.endTime - this.minimumBoundary) / this.boundarySpan * 100; + return {start: start, end: end}; + }, + + reset: function() + { + delete this.minimumBoundary; + delete this.maximumBoundary; + }, + + updateBoundaries: function(record) + { + if (typeof this.minimumBoundary === "undefined" || record.startTime < this.minimumBoundary) { + this.minimumBoundary = record.startTime; + return true; + } + if (typeof this.maximumBoundary === "undefined" || record.endTime > this.maximumBoundary) { + this.maximumBoundary = record.endTime; + return true; + } + return false; + }, + + get boundarySpan() + { + return this.maximumBoundary - this.minimumBoundary; + }, + + formatValue: function(value) + { + return Number.secondsToString(value, WebInspector.UIString); + } +} + + +WebInspector.TimelineCategoryGraph = function(category, isEven) +{ + this._category = category; + + this._graphElement = document.createElement("div"); + this._graphElement.className = "timeline-graph-side timeline-overview-graph-side" + (isEven ? " even" : ""); + + this._barAreaElement = document.createElement("div"); + this._barAreaElement.className = "timeline-graph-bar-area timeline-category-" + category.name; + this._graphElement.appendChild(this._barAreaElement); +} + +WebInspector.TimelineCategoryGraph.prototype = { + get graphElement() + { + return this._graphElement; + }, + + addChunk: function(start, end) + { + var chunk = document.createElement("div"); + chunk.className = "timeline-graph-bar"; + this._barAreaElement.appendChild(chunk); + chunk.style.setProperty("left", start + "%"); + chunk.style.setProperty("width", (end - start) + "%"); + }, + + clearChunks: function() + { + this._barAreaElement.removeChildren(); + }, + + set dimmed(dimmed) + { + if (dimmed) + this._barAreaElement.removeStyleClass("timeline-category-" + this._category.name); + else + this._barAreaElement.addStyleClass("timeline-category-" + this._category.name); + } +} + +WebInspector.TimelinePanel.WindowSelector = function(parent, position, event) +{ + this._startPosition = position; + this._width = parent.offsetWidth; + this._windowSelector = document.createElement("div"); + this._windowSelector.className = "timeline-window-selector"; + this._windowSelector.style.left = this._startPosition + "px"; + this._windowSelector.style.right = this._width - this._startPosition + + "px"; + parent.appendChild(this._windowSelector); +} + +WebInspector.TimelinePanel.WindowSelector.prototype = { + _createSelectorElement: function(parent, left, width, height) + { + var selectorElement = document.createElement("div"); + selectorElement.className = "timeline-window-selector"; + selectorElement.style.left = left + "px"; + selectorElement.style.width = width + "px"; + selectorElement.style.top = "0px"; + selectorElement.style.height = height + "px"; + parent.appendChild(selectorElement); + return selectorElement; + }, + + _close: function(position) + { + position = Math.max(0, Math.min(position, this._width)); + this._windowSelector.parentNode.removeChild(this._windowSelector); + return this._startPosition < position ? {start: this._startPosition, end: position} : {start: position, end: this._startPosition}; + }, + + _updatePosition: function(position) + { + position = Math.max(0, Math.min(position, this._width)); + if (position < this._startPosition) { + this._windowSelector.style.left = position + "px"; + this._windowSelector.style.right = this._width - this._startPosition + "px"; + } else { + this._windowSelector.style.left = this._startPosition + "px"; + this._windowSelector.style.right = this._width - position + "px"; + } + } +} + +WebInspector.HeapGraph = function() { + this._canvas = document.createElement("canvas"); + + this._maxHeapSizeLabel = document.createElement("div"); + this._maxHeapSizeLabel.addStyleClass("memory-graph-label"); + + this._element = document.createElement("div"); + this._element.addStyleClass("hidden"); + this._element.appendChild(this._canvas); + this._element.appendChild(this._maxHeapSizeLabel); +} + +WebInspector.HeapGraph.prototype = { + get element() { + // return this._canvas; + return this._element; + }, + + get visible() { + return !this.element.hasStyleClass("hidden"); + }, + + show: function() { + this.element.removeStyleClass("hidden"); + }, + + hide: function() { + this.element.addStyleClass("hidden"); + }, + + setSize: function(w, h) { + this._canvas.width = w; + this._canvas.height = h - 5; + }, + + update: function(records) + { + if (!records.length) + return; + + var maxTotalHeapSize = 0; + var minTime; + var maxTime; + this._forAllRecords(records, function(r) { + if (r.totalHeapSize && r.totalHeapSize > maxTotalHeapSize) + maxTotalHeapSize = r.totalHeapSize; + + if (typeof minTime === "undefined" || r.startTime < minTime) + minTime = r.startTime; + if (typeof maxTime === "undefined" || r.endTime > maxTime) + maxTime = r.endTime; + }); + + var width = this._canvas.width; + var height = this._canvas.height; + var xFactor = width / (maxTime - minTime); + var yFactor = height / maxTotalHeapSize; + + var histogram = new Array(width); + this._forAllRecords(records, function(r) { + if (!r.usedHeapSize) + return; + var x = Math.round((r.endTime - minTime) * xFactor); + var y = Math.round(r.usedHeapSize * yFactor); + histogram[x] = Math.max(histogram[x] || 0, y); + }); + + var ctx = this._canvas.getContext("2d"); + this._clear(ctx); + + // +1 so that the border always fit into the canvas area. + 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(maxTotalHeapSize); + }, + + _clear: function(ctx) { + ctx.fillStyle = "rgba(255,255,255,0.8)"; + ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); + }, + + _forAllRecords: WebInspector.TimelineOverviewPane.prototype._forAllRecords +} |