diff options
Diffstat (limited to 'WebCore/page/inspector/NetworkPanel.js')
-rw-r--r-- | WebCore/page/inspector/NetworkPanel.js | 1036 |
1 files changed, 1036 insertions, 0 deletions
diff --git a/WebCore/page/inspector/NetworkPanel.js b/WebCore/page/inspector/NetworkPanel.js new file mode 100644 index 0000000..2f000ee --- /dev/null +++ b/WebCore/page/inspector/NetworkPanel.js @@ -0,0 +1,1036 @@ +/* + * Copyright (C) 2007 Apple 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: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.NetworkPanel = function() +{ + WebInspector.Panel.call(this); + + this.timelineEntries = []; + + this.timelineElement = document.createElement("div"); + this.timelineElement.className = "network-timeline"; + this.element.appendChild(this.timelineElement); + + this.summaryElement = document.createElement("div"); + this.summaryElement.className = "network-summary"; + this.element.appendChild(this.summaryElement); + + this.dividersElement = document.createElement("div"); + this.dividersElement.className = "network-dividers"; + this.timelineElement.appendChild(this.dividersElement); + + this.resourcesElement = document.createElement("div"); + this.resourcesElement.className = "network-resources"; + this.resourcesElement.addEventListener("click", this.resourcesClicked.bind(this), false); + this.timelineElement.appendChild(this.resourcesElement); + + var graphArea = document.createElement("div"); + graphArea.className = "network-graph-area"; + this.summaryElement.appendChild(graphArea); + + this.graphLabelElement = document.createElement("div"); + this.graphLabelElement.className = "network-graph-label"; + graphArea.appendChild(this.graphLabelElement); + + this.graphModeSelectElement = document.createElement("select"); + this.graphModeSelectElement.className = "network-graph-mode"; + this.graphModeSelectElement.addEventListener("change", this.changeGraphMode.bind(this), false); + this.graphLabelElement.appendChild(this.graphModeSelectElement); + this.graphLabelElement.appendChild(document.createElement("br")); + + var sizeOptionElement = document.createElement("option"); + sizeOptionElement.calculator = new WebInspector.TransferSizeCalculator(); + sizeOptionElement.textContent = sizeOptionElement.calculator.title; + this.graphModeSelectElement.appendChild(sizeOptionElement); + + var timeOptionElement = document.createElement("option"); + timeOptionElement.calculator = new WebInspector.TransferTimeCalculator(); + timeOptionElement.textContent = timeOptionElement.calculator.title; + this.graphModeSelectElement.appendChild(timeOptionElement); + + var graphSideElement = document.createElement("div"); + graphSideElement.className = "network-graph-side"; + graphArea.appendChild(graphSideElement); + + this.summaryGraphElement = document.createElement("canvas"); + this.summaryGraphElement.setAttribute("width", "450"); + this.summaryGraphElement.setAttribute("height", "38"); + this.summaryGraphElement.className = "network-summary-graph"; + graphSideElement.appendChild(this.summaryGraphElement); + + this.legendElement = document.createElement("div"); + this.legendElement.className = "network-graph-legend"; + graphSideElement.appendChild(this.legendElement); + + this.drawSummaryGraph(); // draws an empty graph + + this.needsRefresh = true; +} + +WebInspector.NetworkPanel.prototype = { + show: function() + { + WebInspector.Panel.prototype.show.call(this); + WebInspector.networkListItem.select(); + this.refreshIfNeeded(); + }, + + hide: function() + { + WebInspector.Panel.prototype.hide.call(this); + WebInspector.networkListItem.deselect(); + }, + + resize: function() + { + this.updateTimelineDividersIfNeeded(); + }, + + resourcesClicked: function(event) + { + // If the click wasn't inside a network resource row, ignore it. + var resourceElement = event.target.firstParentOrSelfWithClass("network-resource"); + if (!resourceElement) + return; + + // If the click was within the network info element, ignore it. + var networkInfo = event.target.firstParentOrSelfWithClass("network-info"); + if (networkInfo) + return; + + // If the click was within the tip balloon element, hide it. + var balloon = event.target.firstParentOrSelfWithClass("tip-balloon"); + if (balloon) { + resourceElement.timelineEntry.showingTipBalloon = false; + return; + } + + resourceElement.timelineEntry.toggleShowingInfo(); + }, + + changeGraphMode: function(event) + { + this.updateSummaryGraph(); + }, + + get calculator() + { + return this.graphModeSelectElement.options[this.graphModeSelectElement.selectedIndex].calculator; + }, + + get totalDuration() + { + return this.latestEndTime - this.earliestStartTime; + }, + + get needsRefresh() + { + return this._needsRefresh; + }, + + set needsRefresh(x) + { + if (this._needsRefresh === x) + return; + this._needsRefresh = x; + if (x && this.visible) + this.refresh(); + }, + + refreshIfNeeded: function() + { + if (this.needsRefresh) + this.refresh(); + }, + + refresh: function() + { + this.needsRefresh = false; + + // calling refresh will call updateTimelineBoundriesIfNeeded, which can clear needsRefresh for future entries, + // so find all the entries that needs refresh first, then loop back trough them to call refresh + var entriesNeedingRefresh = []; + var entriesLength = this.timelineEntries.length; + for (var i = 0; i < entriesLength; ++i) { + var entry = this.timelineEntries[i]; + if (entry.needsRefresh || entry.infoNeedsRefresh) + entriesNeedingRefresh.push(entry); + } + + entriesLength = entriesNeedingRefresh.length; + for (var i = 0; i < entriesLength; ++i) + entriesNeedingRefresh[i].refresh(false, true, true); + + this.updateTimelineDividersIfNeeded(); + this.sortTimelineEntriesIfNeeded(); + this.updateSummaryGraph(); + }, + + makeLegendElement: function(label, value, color) + { + var legendElement = document.createElement("label"); + legendElement.className = "network-graph-legend-item"; + + if (color) { + var swatch = document.createElement("canvas"); + swatch.className = "network-graph-legend-swatch"; + swatch.setAttribute("width", "13"); + swatch.setAttribute("height", "24"); + + legendElement.appendChild(swatch); + + this.drawSwatch(swatch, color); + } + + var labelElement = document.createElement("div"); + labelElement.className = "network-graph-legend-label"; + legendElement.appendChild(labelElement); + + var headerElement = document.createElement("div"); + var headerElement = document.createElement("div"); + headerElement.className = "network-graph-legend-header"; + headerElement.textContent = label; + labelElement.appendChild(headerElement); + + var valueElement = document.createElement("div"); + valueElement.className = "network-graph-legend-value"; + valueElement.textContent = value; + labelElement.appendChild(valueElement); + + return legendElement; + }, + + sortTimelineEntriesSoonIfNeeded: function() + { + if ("sortTimelineEntriesTimeout" in this) + return; + this.sortTimelineEntriesTimeout = setTimeout(this.sortTimelineEntriesIfNeeded.bind(this), 500); + }, + + sortTimelineEntriesIfNeeded: function() + { + if ("sortTimelineEntriesTimeout" in this) { + clearTimeout(this.sortTimelineEntriesTimeout); + delete this.sortTimelineEntriesTimeout; + } + + this.timelineEntries.sort(WebInspector.NetworkPanel.timelineEntryCompare); + + var nextSibling = null; + for (var i = (this.timelineEntries.length - 1); i >= 0; --i) { + var entry = this.timelineEntries[i]; + if (entry.resourceElement.nextSibling !== nextSibling) + this.resourcesElement.insertBefore(entry.resourceElement, nextSibling); + nextSibling = entry.resourceElement; + } + }, + + updateTimelineBoundriesIfNeeded: function(resource, immediate) + { + var didUpdate = false; + if (resource.startTime !== -1 && (this.earliestStartTime === undefined || resource.startTime < this.earliestStartTime)) { + this.earliestStartTime = resource.startTime; + didUpdate = true; + } + + if (resource.endTime !== -1 && (this.latestEndTime === undefined || resource.endTime > this.latestEndTime)) { + this.latestEndTime = resource.endTime; + didUpdate = true; + } + + if (didUpdate) { + if (immediate) { + this.refreshAllTimelineEntries(true, true, immediate); + this.updateTimelineDividersIfNeeded(); + } else { + this.refreshAllTimelineEntriesSoon(true, true, immediate); + this.updateTimelineDividersSoonIfNeeded(); + } + } + + return didUpdate; + }, + + updateTimelineDividersSoonIfNeeded: function() + { + if ("updateTimelineDividersTimeout" in this) + return; + this.updateTimelineDividersTimeout = setTimeout(this.updateTimelineDividersIfNeeded.bind(this), 500); + }, + + updateTimelineDividersIfNeeded: function() + { + if ("updateTimelineDividersTimeout" in this) { + clearTimeout(this.updateTimelineDividersTimeout); + delete this.updateTimelineDividersTimeout; + } + + if (!this.visible) { + this.needsRefresh = true; + return; + } + + if (document.body.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet, so we need to update later. + setTimeout(this.updateTimelineDividersIfNeeded.bind(this), 0); + return; + } + + var dividerCount = Math.round(this.dividersElement.offsetWidth / 64); + var timeSlice = this.totalDuration / dividerCount; + + if (this.lastDividerTimeSlice === timeSlice) + return; + + this.lastDividerTimeSlice = timeSlice; + + this.dividersElement.removeChildren(); + + for (var i = 1; i <= dividerCount; ++i) { + var divider = document.createElement("div"); + divider.className = "network-divider"; + if (i === dividerCount) + divider.addStyleClass("last"); + divider.style.left = ((i / dividerCount) * 100) + "%"; + + var label = document.createElement("div"); + label.className = "network-divider-label"; + label.textContent = Number.secondsToString(timeSlice * i); + divider.appendChild(label); + + this.dividersElement.appendChild(divider); + } + }, + + refreshAllTimelineEntriesSoon: function(skipBoundryUpdate, skipTimelineSort, immediate) + { + if ("refreshAllTimelineEntriesTimeout" in this) + return; + this.refreshAllTimelineEntriesTimeout = setTimeout(this.refreshAllTimelineEntries.bind(this), 500, skipBoundryUpdate, skipTimelineSort, immediate); + }, + + refreshAllTimelineEntries: function(skipBoundryUpdate, skipTimelineSort, immediate) + { + if ("refreshAllTimelineEntriesTimeout" in this) { + clearTimeout(this.refreshAllTimelineEntriesTimeout); + delete this.refreshAllTimelineEntriesTimeout; + } + + var entriesLength = this.timelineEntries.length; + for (var i = 0; i < entriesLength; ++i) + this.timelineEntries[i].refresh(skipBoundryUpdate, skipTimelineSort, immediate); + }, + + fadeOutRect: function(ctx, x, y, w, h, a1, a2) + { + ctx.save(); + + var gradient = ctx.createLinearGradient(x, y, x, y + h); + gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")"); + gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")"); + gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)"); + + ctx.globalCompositeOperation = "destination-out"; + + ctx.fillStyle = gradient; + ctx.fillRect(x, y, w, h); + + ctx.restore(); + }, + + drawSwatch: function(canvas, color) + { + var ctx = canvas.getContext("2d"); + + function drawSwatchSquare() { + ctx.fillStyle = color; + ctx.fillRect(0, 0, 13, 13); + + var gradient = ctx.createLinearGradient(0, 0, 13, 13); + gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)"); + gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)"); + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, 13, 13); + + gradient = ctx.createLinearGradient(13, 13, 0, 0); + gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)"); + gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)"); + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, 13, 13); + + ctx.strokeStyle = "rgba(0, 0, 0, 0.6)"; + ctx.strokeRect(0.5, 0.5, 12, 12); + } + + ctx.clearRect(0, 0, 13, 24); + + drawSwatchSquare(); + + ctx.save(); + + ctx.translate(0, 25); + ctx.scale(1, -1); + + drawSwatchSquare(); + + ctx.restore(); + + this.fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0); + }, + + drawSummaryGraph: function(segments) + { + if (!this.summaryGraphElement) + return; + + if (!segments || !segments.length) + segments = [{color: "white", value: 1}]; + + // Calculate the total of all segments. + var total = 0; + for (var i = 0; i < segments.length; ++i) + total += segments[i].value; + + // Calculate the percentage of each segment, rounded to the nearest percent. + var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) }); + + // Calculate the total percentage. + var percentTotal = 0; + for (var i = 0; i < percents.length; ++i) + percentTotal += percents[i]; + + // Make sure our percentage total is not greater-than 100, it can be greater + // if we rounded up for a few segments. + while (percentTotal > 100) { + for (var i = 0; i < percents.length && percentTotal > 100; ++i) { + if (percents[i] > 1) { + --percents[i]; + --percentTotal; + } + } + } + + // Make sure our percentage total is not less-than 100, it can be less + // if we rounded down for a few segments. + while (percentTotal < 100) { + for (var i = 0; i < percents.length && percentTotal < 100; ++i) { + ++percents[i]; + ++percentTotal; + } + } + + var ctx = this.summaryGraphElement.getContext("2d"); + + var x = 0; + var y = 0; + var w = 450; + var h = 19; + var r = (h / 2); + + function drawPillShadow() + { + // This draws a line with a shadow that is offset away from the line. The line is stroked + // twice with different X shadow offsets to give more feathered edges. Later we erase the + // line with destination-out 100% transparent black, leaving only the shadow. This only + // works if nothing has been drawn into the canvas yet. + + ctx.beginPath(); + ctx.moveTo(x + 4, y + h - 3 - 0.5); + ctx.lineTo(x + w - 4, y + h - 3 - 0.5); + ctx.closePath(); + + ctx.save(); + + ctx.shadowBlur = 2; + ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; + ctx.shadowOffsetX = 3; + ctx.shadowOffsetY = 5; + + ctx.strokeStyle = "white"; + ctx.lineWidth = 1; + + ctx.stroke(); + + ctx.shadowOffsetX = -3; + + ctx.stroke(); + + ctx.restore(); + + ctx.save(); + + ctx.globalCompositeOperation = "destination-out"; + ctx.strokeStyle = "rgba(0, 0, 0, 1)"; + ctx.lineWidth = 1; + + ctx.stroke(); + + ctx.restore(); + } + + function drawPill() + { + // Make a rounded rect path. + ctx.beginPath(); + ctx.moveTo(x, y + r); + ctx.lineTo(x, y + h - r); + ctx.quadraticCurveTo(x, y + h, x + r, y + h); + ctx.lineTo(x + w - r, y + h); + ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - r); + ctx.lineTo(x + w, y + r); + ctx.quadraticCurveTo(x + w, y, x + w - r, y); + ctx.lineTo(x + r, y); + ctx.quadraticCurveTo(x, y, x, y + r); + ctx.closePath(); + + // Clip to the rounded rect path. + ctx.save(); + ctx.clip(); + + // Fill the segments with the associated color. + var previousSegmentsWidth = 0; + for (var i = 0; i < segments.length; ++i) { + var segmentWidth = Math.round(w * percents[i] / 100); + ctx.fillStyle = segments[i].color; + ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h); + previousSegmentsWidth += segmentWidth; + } + + // Draw the segment divider lines. + ctx.lineWidth = 1; + for (var i = 1; i < 20; ++i) { + ctx.beginPath(); + ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y); + ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h); + ctx.closePath(); + + ctx.strokeStyle = "rgba(0, 0, 0, 0.2)"; + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y); + ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h); + ctx.closePath(); + + ctx.strokeStyle = "rgba(255, 255, 255, 0.2)"; + ctx.stroke(); + } + + // Draw the pill shading. + var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5)); + lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)"); + lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)"); + lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)"); + + var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h); + darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)"); + darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)"); + darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)"); + + ctx.fillStyle = darkGradient; + ctx.fillRect(x, y, w, h); + + ctx.fillStyle = lightGradient; + ctx.fillRect(x, y, w, h); + + ctx.restore(); + } + + ctx.clearRect(x, y, w, (h * 2)); + + drawPillShadow(); + drawPill(); + + ctx.save(); + + ctx.translate(0, (h * 2) + 1); + ctx.scale(1, -1); + + drawPill(); + + ctx.restore(); + + this.fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0); + }, + + updateSummaryGraphSoon: function() + { + if ("updateSummaryGraphTimeout" in this) + return; + this.updateSummaryGraphTimeout = setTimeout(this.updateSummaryGraph.bind(this), 500); + }, + + updateSummaryGraph: function() + { + if ("updateSummaryGraphTimeout" in this) { + clearTimeout(this.updateSummaryGraphTimeout); + delete this.updateSummaryGraphTimeout; + } + + var graphInfo = this.calculator.computeValues(this.timelineEntries); + + var categoryOrder = ["documents", "stylesheets", "images", "scripts", "fonts", "other"]; + var categoryColors = {documents: {r: 47, g: 102, b: 236}, stylesheets: {r: 157, g: 231, b: 119}, images: {r: 164, g: 60, b: 255}, scripts: {r: 255, g: 121, b: 0}, fonts: {r: 231, g: 231, b: 10}, other: {r: 186, g: 186, b: 186}}; + var fillSegments = []; + + this.legendElement.removeChildren(); + + if (this.totalLegendLabel) + this.totalLegendLabel.parentNode.removeChild(this.totalLegendLabel); + + this.totalLegendLabel = this.makeLegendElement(this.calculator.totalTitle, this.calculator.formatValue(graphInfo.total)); + this.totalLegendLabel.addStyleClass("network-graph-legend-total"); + this.graphLabelElement.appendChild(this.totalLegendLabel); + + for (var i = 0; i < categoryOrder.length; ++i) { + var category = categoryOrder[i]; + var size = graphInfo.categoryValues[category]; + if (!size) + continue; + + var color = categoryColors[category]; + var colorString = "rgb(" + color.r + ", " + color.g + ", " + color.b + ")"; + + var fillSegment = {color: colorString, value: size}; + fillSegments.push(fillSegment); + + var legendLabel = this.makeLegendElement(WebInspector.resourceCategories[category].title, this.calculator.formatValue(size), colorString); + this.legendElement.appendChild(legendLabel); + } + + this.drawSummaryGraph(fillSegments); + }, + + clearTimeline: function() + { + delete this.earliestStartTime; + delete this.latestEndTime; + + var entriesLength = this.timelineEntries.length; + for (var i = 0; i < entriesLength; ++i) + delete this.timelineEntries[i].resource.networkTimelineEntry; + + this.timelineEntries = []; + this.resourcesElement.removeChildren(); + + this.drawSummaryGraph(); // draws an empty graph + }, + + addResourceToTimeline: function(resource) + { + var timelineEntry = new WebInspector.NetworkTimelineEntry(this, resource); + this.timelineEntries.push(timelineEntry); + this.resourcesElement.appendChild(timelineEntry.resourceElement); + + timelineEntry.refresh(); + this.updateSummaryGraphSoon(); + } +} + +WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype; + +WebInspector.NetworkPanel.timelineEntryCompare = function(a, b) +{ + if (a.resource.startTime < b.resource.startTime) + return -1; + if (a.resource.startTime > b.resource.startTime) + return 1; + if (a.resource.endTime < b.resource.endTime) + return -1; + if (a.resource.endTime > b.resource.endTime) + return 1; + return 0; +} + +WebInspector.NetworkTimelineEntry = function(panel, resource) +{ + this.panel = panel; + this.resource = resource; + resource.networkTimelineEntry = this; + + this.resourceElement = document.createElement("div"); + this.resourceElement.className = "network-resource"; + this.resourceElement.timelineEntry = this; + + this.titleElement = document.createElement("div"); + this.titleElement.className = "network-title"; + this.resourceElement.appendChild(this.titleElement); + + this.fileElement = document.createElement("div"); + this.fileElement.className = "network-file"; + this.fileElement.innerHTML = WebInspector.linkifyURL(resource.url, resource.displayName); + this.titleElement.appendChild(this.fileElement); + + this.tipButtonElement = document.createElement("button"); + this.tipButtonElement.className = "tip-button"; + this.showingTipButton = this.resource.tips.length; + this.fileElement.insertBefore(this.tipButtonElement, this.fileElement.firstChild); + + this.tipButtonElement.addEventListener("click", this.toggleTipBalloon.bind(this), false ); + + this.areaElement = document.createElement("div"); + this.areaElement.className = "network-area"; + this.titleElement.appendChild(this.areaElement); + + this.barElement = document.createElement("div"); + this.areaElement.appendChild(this.barElement); + + this.infoElement = document.createElement("div"); + this.infoElement.className = "network-info hidden"; + this.resourceElement.appendChild(this.infoElement); +} + +WebInspector.NetworkTimelineEntry.prototype = { + refresh: function(skipBoundryUpdate, skipTimelineSort, immediate) + { + if (!this.panel.visible) { + this.needsRefresh = true; + this.panel.needsRefresh = true; + return; + } + + delete this.needsRefresh; + + if (!skipBoundryUpdate) { + if (this.panel.updateTimelineBoundriesIfNeeded(this.resource, immediate)) + return; // updateTimelineBoundriesIfNeeded calls refresh() on all entries, so we can just return + } + + if (!skipTimelineSort) { + if (immediate) + this.panel.sortTimelineEntriesIfNeeded(); + else + this.panel.sortTimelineEntriesSoonIfNeeded(); + } + + if (this.resource.startTime !== -1) { + var percentStart = ((this.resource.startTime - this.panel.earliestStartTime) / this.panel.totalDuration) * 100; + this.barElement.style.left = percentStart + "%"; + } else { + this.barElement.style.left = null; + } + + if (this.resource.endTime !== -1) { + var percentEnd = ((this.panel.latestEndTime - this.resource.endTime) / this.panel.totalDuration) * 100; + this.barElement.style.right = percentEnd + "%"; + } else { + this.barElement.style.right = "0px"; + } + + this.barElement.className = "network-bar network-category-" + this.resource.category.name; + + if (this.infoNeedsRefresh) + this.refreshInfo(); + }, + + refreshInfo: function() + { + if (!this.showingInfo) { + this.infoNeedsRefresh = true; + return; + } + + if (!this.panel.visible) { + this.panel.needsRefresh = true; + this.infoNeedsRefresh = true; + return; + } + + this.infoNeedsRefresh = false; + + this.infoElement.removeChildren(); + + var sections = [ + {title: WebInspector.UIString("Request"), info: this.resource.sortedRequestHeaders}, + {title: WebInspector.UIString("Response"), info: this.resource.sortedResponseHeaders} + ]; + + function createSectionTable(section) + { + if (!section.info.length) + return; + + var table = document.createElement("table"); + this.infoElement.appendChild(table); + + var heading = document.createElement("th"); + heading.textContent = section.title; + + var row = table.createTHead().insertRow(-1).appendChild(heading); + var body = document.createElement("tbody"); + table.appendChild(body); + + section.info.forEach(function(header) { + var row = body.insertRow(-1); + var th = document.createElement("th"); + th.textContent = header.header; + row.appendChild(th); + row.insertCell(-1).textContent = header.value; + }); + } + + sections.forEach(createSectionTable, this); + }, + + refreshInfoIfNeeded: function() + { + if (this.infoNeedsRefresh === false) + return; + + this.refreshInfo(); + }, + + toggleShowingInfo: function() + { + this.showingInfo = !this.showingInfo; + }, + + get showingInfo() + { + return this._showingInfo; + }, + + set showingInfo(x) + { + if (this._showingInfo === x) + return; + + this._showingInfo = x; + + var element = this.infoElement; + if (x) { + element.removeStyleClass("hidden"); + element.style.setProperty("overflow", "hidden"); + this.refreshInfoIfNeeded(); + WebInspector.animateStyle([{element: element, start: {height: 0}, end: {height: element.offsetHeight}}], 250, function() { element.style.removeProperty("height"); element.style.removeProperty("overflow") }); + } else { + element.style.setProperty("overflow", "hidden"); + WebInspector.animateStyle([{element: element, end: {height: 0}}], 250, function() { element.addStyleClass("hidden"); element.style.removeProperty("height") }); + } + }, + + get showingTipButton() + { + return !this.tipButtonElement.hasStyleClass("hidden"); + }, + + set showingTipButton(x) + { + if (x) + this.tipButtonElement.removeStyleClass("hidden"); + else + this.tipButtonElement.addStyleClass("hidden"); + }, + + toggleTipBalloon: function(event) + { + this.showingTipBalloon = !this.showingTipBalloon; + event.stopPropagation(); + }, + + get showingTipBalloon() + { + return this._showingTipBalloon; + }, + + set showingTipBalloon(x) + { + if (this._showingTipBalloon === x) + return; + + this._showingTipBalloon = x; + + if (x) { + if (!this.tipBalloonElement) { + this.tipBalloonElement = document.createElement("div"); + this.tipBalloonElement.className = "tip-balloon"; + this.titleElement.appendChild(this.tipBalloonElement); + + this.tipBalloonContentElement = document.createElement("div"); + this.tipBalloonContentElement.className = "tip-balloon-content"; + this.tipBalloonElement.appendChild(this.tipBalloonContentElement); + var tipText = ""; + for (var id in this.resource.tips) + tipText += this.resource.tips[id].message + "\n"; + this.tipBalloonContentElement.textContent = tipText; + } + + this.tipBalloonElement.removeStyleClass("hidden"); + WebInspector.animateStyle([{element: this.tipBalloonElement, start: {left: 160, opacity: 0}, end: {left: 145, opacity: 1}}], 250); + } else { + var element = this.tipBalloonElement; + WebInspector.animateStyle([{element: this.tipBalloonElement, start: {left: 145, opacity: 1}, end: {left: 160, opacity: 0}}], 250, function() { element.addStyleClass("hidden") }); + } + } +} + +WebInspector.TimelineValueCalculator = function() +{ +} + +WebInspector.TimelineValueCalculator.prototype = { + computeValues: function(entries) + { + var total = 0; + var categoryValues = {}; + + function compute(entry) + { + var value = this._value(entry); + if (value === undefined) + return; + + if (!(entry.resource.category.name in categoryValues)) + categoryValues[entry.resource.category.name] = 0; + categoryValues[entry.resource.category.name] += value; + total += value; + } + entries.forEach(compute, this); + + return {categoryValues: categoryValues, total: total}; + }, + + _value: function(entry) + { + return 0; + }, + + get title() + { + return ""; + }, + + formatValue: function(value) + { + return value.toString(); + } +} + +WebInspector.TransferTimeCalculator = function() +{ + WebInspector.TimelineValueCalculator.call(this); +} + +WebInspector.TransferTimeCalculator.prototype = { + computeValues: function(entries) + { + var entriesByCategory = {}; + entries.forEach(function(entry) { + if (!(entry.resource.category.name in entriesByCategory)) + entriesByCategory[entry.resource.category.name] = []; + entriesByCategory[entry.resource.category.name].push(entry); + }); + + var earliestStart; + var latestEnd; + var categoryValues = {}; + for (var category in entriesByCategory) { + entriesByCategory[category].sort(WebInspector.NetworkPanel.timelineEntryCompare); + categoryValues[category] = 0; + + var segment = {start: -1, end: -1}; + entriesByCategory[category].forEach(function(entry) { + if (entry.resource.startTime == -1 || entry.resource.endTime == -1) + return; + + if (earliestStart === undefined) + earliestStart = entry.resource.startTime; + else + earliestStart = Math.min(earliestStart, entry.resource.startTime); + + if (latestEnd === undefined) + latestEnd = entry.resource.endTime; + else + latestEnd = Math.max(latestEnd, entry.resource.endTime); + + if (entry.resource.startTime <= segment.end) { + segment.end = Math.max(segment.end, entry.resource.endTime); + return; + } + + categoryValues[category] += segment.end - segment.start; + + segment.start = entry.resource.startTime; + segment.end = entry.resource.endTime; + }); + + // Add the last segment + categoryValues[category] += segment.end - segment.start; + } + + return {categoryValues: categoryValues, total: latestEnd - earliestStart}; + }, + + get title() + { + return WebInspector.UIString("Transfer Time"); + }, + + get totalTitle() + { + return WebInspector.UIString("Total Time"); + }, + + formatValue: function(value) + { + return Number.secondsToString(value); + } +} + +WebInspector.TransferTimeCalculator.prototype.__proto__ = WebInspector.TimelineValueCalculator.prototype; + +WebInspector.TransferSizeCalculator = function() +{ + WebInspector.TimelineValueCalculator.call(this); +} + +WebInspector.TransferSizeCalculator.prototype = { + _value: function(entry) + { + return entry.resource.contentLength; + }, + + get title() + { + return WebInspector.UIString("Transfer Size"); + }, + + get totalTitle() + { + return WebInspector.UIString("Total Size"); + }, + + formatValue: function(value) + { + return Number.bytesToString(value); + } +} + +WebInspector.TransferSizeCalculator.prototype.__proto__ = WebInspector.TimelineValueCalculator.prototype; |