2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 WebInspector.NetworkPanel = function()
31 WebInspector.Panel.call(this);
33 this.timelineEntries = [];
35 this.timelineElement = document.createElement("div");
36 this.timelineElement.className = "network-timeline";
37 this.element.appendChild(this.timelineElement);
39 this.summaryElement = document.createElement("div");
40 this.summaryElement.className = "network-summary";
41 this.element.appendChild(this.summaryElement);
43 this.dividersElement = document.createElement("div");
44 this.dividersElement.className = "network-dividers";
45 this.timelineElement.appendChild(this.dividersElement);
47 this.resourcesElement = document.createElement("div");
48 this.resourcesElement.className = "network-resources";
49 this.resourcesElement.addEventListener("click", this.resourcesClicked.bind(this), false);
50 this.timelineElement.appendChild(this.resourcesElement);
52 var graphArea = document.createElement("div");
53 graphArea.className = "network-graph-area";
54 this.summaryElement.appendChild(graphArea);
56 this.graphLabelElement = document.createElement("div");
57 this.graphLabelElement.className = "network-graph-label";
58 graphArea.appendChild(this.graphLabelElement);
60 this.graphModeSelectElement = document.createElement("select");
61 this.graphModeSelectElement.className = "network-graph-mode";
62 this.graphModeSelectElement.addEventListener("change", this.changeGraphMode.bind(this), false);
63 this.graphLabelElement.appendChild(this.graphModeSelectElement);
64 this.graphLabelElement.appendChild(document.createElement("br"));
66 var sizeOptionElement = document.createElement("option");
67 sizeOptionElement.calculator = new WebInspector.TransferSizeCalculator();
68 sizeOptionElement.textContent = sizeOptionElement.calculator.title;
69 this.graphModeSelectElement.appendChild(sizeOptionElement);
71 var timeOptionElement = document.createElement("option");
72 timeOptionElement.calculator = new WebInspector.TransferTimeCalculator();
73 timeOptionElement.textContent = timeOptionElement.calculator.title;
74 this.graphModeSelectElement.appendChild(timeOptionElement);
76 var graphSideElement = document.createElement("div");
77 graphSideElement.className = "network-graph-side";
78 graphArea.appendChild(graphSideElement);
80 this.summaryGraphElement = document.createElement("canvas");
81 this.summaryGraphElement.setAttribute("width", "450");
82 this.summaryGraphElement.setAttribute("height", "38");
83 this.summaryGraphElement.className = "network-summary-graph";
84 graphSideElement.appendChild(this.summaryGraphElement);
86 this.legendElement = document.createElement("div");
87 this.legendElement.className = "network-graph-legend";
88 graphSideElement.appendChild(this.legendElement);
90 this.drawSummaryGraph(); // draws an empty graph
92 this.needsRefresh = true;
95 WebInspector.NetworkPanel.prototype = {
98 WebInspector.Panel.prototype.show.call(this);
99 WebInspector.networkListItem.select();
100 this.refreshIfNeeded();
105 WebInspector.Panel.prototype.hide.call(this);
106 WebInspector.networkListItem.deselect();
111 this.updateTimelineDividersIfNeeded();
114 resourcesClicked: function(event)
116 // If the click wasn't inside a network resource row, ignore it.
117 var resourceElement = event.target.enclosingNodeOrSelfWithClass("network-resource");
118 if (!resourceElement)
121 // If the click was within the network info element, ignore it.
122 var networkInfo = event.target.enclosingNodeOrSelfWithClass("network-info");
126 // If the click was within the tip balloon element, hide it.
127 var balloon = event.target.enclosingNodeOrSelfWithClass("tip-balloon");
129 resourceElement.timelineEntry.showingTipBalloon = false;
133 resourceElement.timelineEntry.toggleShowingInfo();
136 changeGraphMode: function(event)
138 this.updateSummaryGraph();
143 return this.graphModeSelectElement.options[this.graphModeSelectElement.selectedIndex].calculator;
148 return this.latestEndTime - this.earliestStartTime;
153 return this._needsRefresh;
158 if (this._needsRefresh === x)
160 this._needsRefresh = x;
161 if (x && this.visible)
165 refreshIfNeeded: function()
167 if (this.needsRefresh)
173 this.needsRefresh = false;
175 // calling refresh will call updateTimelineBoundriesIfNeeded, which can clear needsRefresh for future entries,
176 // so find all the entries that needs refresh first, then loop back trough them to call refresh
177 var entriesNeedingRefresh = [];
178 var entriesLength = this.timelineEntries.length;
179 for (var i = 0; i < entriesLength; ++i) {
180 var entry = this.timelineEntries[i];
181 if (entry.needsRefresh || entry.infoNeedsRefresh)
182 entriesNeedingRefresh.push(entry);
185 entriesLength = entriesNeedingRefresh.length;
186 for (var i = 0; i < entriesLength; ++i)
187 entriesNeedingRefresh[i].refresh(false, true, true);
189 this.updateTimelineDividersIfNeeded();
190 this.sortTimelineEntriesIfNeeded();
191 this.updateSummaryGraph();
194 makeLegendElement: function(label, value, color)
196 var legendElement = document.createElement("label");
197 legendElement.className = "network-graph-legend-item";
200 var swatch = document.createElement("canvas");
201 swatch.className = "network-graph-legend-swatch";
202 swatch.setAttribute("width", "13");
203 swatch.setAttribute("height", "24");
205 legendElement.appendChild(swatch);
207 this.drawSwatch(swatch, color);
210 var labelElement = document.createElement("div");
211 labelElement.className = "network-graph-legend-label";
212 legendElement.appendChild(labelElement);
214 var headerElement = document.createElement("div");
215 var headerElement = document.createElement("div");
216 headerElement.className = "network-graph-legend-header";
217 headerElement.textContent = label;
218 labelElement.appendChild(headerElement);
220 var valueElement = document.createElement("div");
221 valueElement.className = "network-graph-legend-value";
222 valueElement.textContent = value;
223 labelElement.appendChild(valueElement);
225 return legendElement;
228 sortTimelineEntriesSoonIfNeeded: function()
230 if ("sortTimelineEntriesTimeout" in this)
232 this.sortTimelineEntriesTimeout = setTimeout(this.sortTimelineEntriesIfNeeded.bind(this), 500);
235 sortTimelineEntriesIfNeeded: function()
237 if ("sortTimelineEntriesTimeout" in this) {
238 clearTimeout(this.sortTimelineEntriesTimeout);
239 delete this.sortTimelineEntriesTimeout;
242 this.timelineEntries.sort(WebInspector.NetworkPanel.timelineEntryCompare);
244 var nextSibling = null;
245 for (var i = (this.timelineEntries.length - 1); i >= 0; --i) {
246 var entry = this.timelineEntries[i];
247 if (entry.resourceElement.nextSibling !== nextSibling)
248 this.resourcesElement.insertBefore(entry.resourceElement, nextSibling);
249 nextSibling = entry.resourceElement;
253 updateTimelineBoundriesIfNeeded: function(resource, immediate)
255 var didUpdate = false;
256 if (resource.startTime !== -1 && (this.earliestStartTime === undefined || resource.startTime < this.earliestStartTime)) {
257 this.earliestStartTime = resource.startTime;
261 if (resource.endTime !== -1 && (this.latestEndTime === undefined || resource.endTime > this.latestEndTime)) {
262 this.latestEndTime = resource.endTime;
268 this.refreshAllTimelineEntries(true, true, immediate);
269 this.updateTimelineDividersIfNeeded();
271 this.refreshAllTimelineEntriesSoon(true, true, immediate);
272 this.updateTimelineDividersSoonIfNeeded();
279 updateTimelineDividersSoonIfNeeded: function()
281 if ("updateTimelineDividersTimeout" in this)
283 this.updateTimelineDividersTimeout = setTimeout(this.updateTimelineDividersIfNeeded.bind(this), 500);
286 updateTimelineDividersIfNeeded: function()
288 if ("updateTimelineDividersTimeout" in this) {
289 clearTimeout(this.updateTimelineDividersTimeout);
290 delete this.updateTimelineDividersTimeout;
294 this.needsRefresh = true;
298 if (document.body.offsetWidth <= 0) {
299 // The stylesheet hasn't loaded yet, so we need to update later.
300 setTimeout(this.updateTimelineDividersIfNeeded.bind(this), 0);
304 var dividerCount = Math.round(this.dividersElement.offsetWidth / 64);
305 var timeSlice = this.totalDuration / dividerCount;
307 if (this.lastDividerTimeSlice === timeSlice)
310 this.lastDividerTimeSlice = timeSlice;
312 this.dividersElement.removeChildren();
314 for (var i = 1; i <= dividerCount; ++i) {
315 var divider = document.createElement("div");
316 divider.className = "network-divider";
317 if (i === dividerCount)
318 divider.addStyleClass("last");
319 divider.style.left = ((i / dividerCount) * 100) + "%";
321 var label = document.createElement("div");
322 label.className = "network-divider-label";
323 label.textContent = Number.secondsToString(timeSlice * i);
324 divider.appendChild(label);
326 this.dividersElement.appendChild(divider);
330 refreshAllTimelineEntriesSoon: function(skipBoundryUpdate, skipTimelineSort, immediate)
332 if ("refreshAllTimelineEntriesTimeout" in this)
334 this.refreshAllTimelineEntriesTimeout = setTimeout(this.refreshAllTimelineEntries.bind(this), 500, skipBoundryUpdate, skipTimelineSort, immediate);
337 refreshAllTimelineEntries: function(skipBoundryUpdate, skipTimelineSort, immediate)
339 if ("refreshAllTimelineEntriesTimeout" in this) {
340 clearTimeout(this.refreshAllTimelineEntriesTimeout);
341 delete this.refreshAllTimelineEntriesTimeout;
344 var entriesLength = this.timelineEntries.length;
345 for (var i = 0; i < entriesLength; ++i)
346 this.timelineEntries[i].refresh(skipBoundryUpdate, skipTimelineSort, immediate);
349 fadeOutRect: function(ctx, x, y, w, h, a1, a2)
353 var gradient = ctx.createLinearGradient(x, y, x, y + h);
354 gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")");
355 gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")");
356 gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)");
358 ctx.globalCompositeOperation = "destination-out";
360 ctx.fillStyle = gradient;
361 ctx.fillRect(x, y, w, h);
366 drawSwatch: function(canvas, color)
368 var ctx = canvas.getContext("2d");
370 function drawSwatchSquare() {
371 ctx.fillStyle = color;
372 ctx.fillRect(0, 0, 13, 13);
374 var gradient = ctx.createLinearGradient(0, 0, 13, 13);
375 gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)");
376 gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");
378 ctx.fillStyle = gradient;
379 ctx.fillRect(0, 0, 13, 13);
381 gradient = ctx.createLinearGradient(13, 13, 0, 0);
382 gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)");
383 gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)");
385 ctx.fillStyle = gradient;
386 ctx.fillRect(0, 0, 13, 13);
388 ctx.strokeStyle = "rgba(0, 0, 0, 0.6)";
389 ctx.strokeRect(0.5, 0.5, 12, 12);
392 ctx.clearRect(0, 0, 13, 24);
398 ctx.translate(0, 25);
405 this.fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0);
408 drawSummaryGraph: function(segments)
410 if (!this.summaryGraphElement)
413 if (!segments || !segments.length)
414 segments = [{color: "white", value: 1}];
416 // Calculate the total of all segments.
418 for (var i = 0; i < segments.length; ++i)
419 total += segments[i].value;
421 // Calculate the percentage of each segment, rounded to the nearest percent.
422 var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) });
424 // Calculate the total percentage.
425 var percentTotal = 0;
426 for (var i = 0; i < percents.length; ++i)
427 percentTotal += percents[i];
429 // Make sure our percentage total is not greater-than 100, it can be greater
430 // if we rounded up for a few segments.
431 while (percentTotal > 100) {
432 for (var i = 0; i < percents.length && percentTotal > 100; ++i) {
433 if (percents[i] > 1) {
440 // Make sure our percentage total is not less-than 100, it can be less
441 // if we rounded down for a few segments.
442 while (percentTotal < 100) {
443 for (var i = 0; i < percents.length && percentTotal < 100; ++i) {
449 var ctx = this.summaryGraphElement.getContext("2d");
457 function drawPillShadow()
459 // This draws a line with a shadow that is offset away from the line. The line is stroked
460 // twice with different X shadow offsets to give more feathered edges. Later we erase the
461 // line with destination-out 100% transparent black, leaving only the shadow. This only
462 // works if nothing has been drawn into the canvas yet.
465 ctx.moveTo(x + 4, y + h - 3 - 0.5);
466 ctx.lineTo(x + w - 4, y + h - 3 - 0.5);
472 ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
473 ctx.shadowOffsetX = 3;
474 ctx.shadowOffsetY = 5;
476 ctx.strokeStyle = "white";
481 ctx.shadowOffsetX = -3;
489 ctx.globalCompositeOperation = "destination-out";
490 ctx.strokeStyle = "rgba(0, 0, 0, 1)";
500 // Make a rounded rect path.
502 ctx.moveTo(x, y + r);
503 ctx.lineTo(x, y + h - r);
504 ctx.quadraticCurveTo(x, y + h, x + r, y + h);
505 ctx.lineTo(x + w - r, y + h);
506 ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - r);
507 ctx.lineTo(x + w, y + r);
508 ctx.quadraticCurveTo(x + w, y, x + w - r, y);
509 ctx.lineTo(x + r, y);
510 ctx.quadraticCurveTo(x, y, x, y + r);
513 // Clip to the rounded rect path.
517 // Fill the segments with the associated color.
518 var previousSegmentsWidth = 0;
519 for (var i = 0; i < segments.length; ++i) {
520 var segmentWidth = Math.round(w * percents[i] / 100);
521 ctx.fillStyle = segments[i].color;
522 ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h);
523 previousSegmentsWidth += segmentWidth;
526 // Draw the segment divider lines.
528 for (var i = 1; i < 20; ++i) {
530 ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y);
531 ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h);
534 ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
538 ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y);
539 ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h);
542 ctx.strokeStyle = "rgba(255, 255, 255, 0.2)";
546 // Draw the pill shading.
547 var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5));
548 lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)");
549 lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)");
550 lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");
552 var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h);
553 darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)");
554 darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)");
555 darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)");
557 ctx.fillStyle = darkGradient;
558 ctx.fillRect(x, y, w, h);
560 ctx.fillStyle = lightGradient;
561 ctx.fillRect(x, y, w, h);
566 ctx.clearRect(x, y, w, (h * 2));
573 ctx.translate(0, (h * 2) + 1);
580 this.fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0);
583 updateSummaryGraphSoon: function()
585 if ("updateSummaryGraphTimeout" in this)
587 this.updateSummaryGraphTimeout = setTimeout(this.updateSummaryGraph.bind(this), 500);
590 updateSummaryGraph: function()
592 if ("updateSummaryGraphTimeout" in this) {
593 clearTimeout(this.updateSummaryGraphTimeout);
594 delete this.updateSummaryGraphTimeout;
597 var graphInfo = this.calculator.computeValues(this.timelineEntries);
599 var categoryOrder = ["documents", "stylesheets", "images", "scripts", "fonts", "other"];
600 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}};
601 var fillSegments = [];
603 this.legendElement.removeChildren();
605 if (this.totalLegendLabel)
606 this.totalLegendLabel.parentNode.removeChild(this.totalLegendLabel);
608 this.totalLegendLabel = this.makeLegendElement(this.calculator.totalTitle, this.calculator.formatValue(graphInfo.total));
609 this.totalLegendLabel.addStyleClass("network-graph-legend-total");
610 this.graphLabelElement.appendChild(this.totalLegendLabel);
612 for (var i = 0; i < categoryOrder.length; ++i) {
613 var category = categoryOrder[i];
614 var size = graphInfo.categoryValues[category];
618 var color = categoryColors[category];
619 var colorString = "rgb(" + color.r + ", " + color.g + ", " + color.b + ")";
621 var fillSegment = {color: colorString, value: size};
622 fillSegments.push(fillSegment);
624 var legendLabel = this.makeLegendElement(WebInspector.resourceCategories[category].title, this.calculator.formatValue(size), colorString);
625 this.legendElement.appendChild(legendLabel);
628 this.drawSummaryGraph(fillSegments);
631 clearTimeline: function()
633 delete this.earliestStartTime;
634 delete this.latestEndTime;
636 var entriesLength = this.timelineEntries.length;
637 for (var i = 0; i < entriesLength; ++i)
638 delete this.timelineEntries[i].resource.networkTimelineEntry;
640 this.timelineEntries = [];
641 this.resourcesElement.removeChildren();
643 this.drawSummaryGraph(); // draws an empty graph
646 addResourceToTimeline: function(resource)
648 var timelineEntry = new WebInspector.NetworkTimelineEntry(this, resource);
649 this.timelineEntries.push(timelineEntry);
650 this.resourcesElement.appendChild(timelineEntry.resourceElement);
652 timelineEntry.refresh();
653 this.updateSummaryGraphSoon();
657 WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype;
659 WebInspector.NetworkPanel.timelineEntryCompare = function(a, b)
661 if (a.resource.startTime < b.resource.startTime)
663 if (a.resource.startTime > b.resource.startTime)
665 if (a.resource.endTime < b.resource.endTime)
667 if (a.resource.endTime > b.resource.endTime)
672 WebInspector.NetworkTimelineEntry = function(panel, resource)
675 this.resource = resource;
676 resource.networkTimelineEntry = this;
678 this.resourceElement = document.createElement("div");
679 this.resourceElement.className = "network-resource";
680 this.resourceElement.timelineEntry = this;
682 this.titleElement = document.createElement("div");
683 this.titleElement.className = "network-title";
684 this.resourceElement.appendChild(this.titleElement);
686 this.fileElement = document.createElement("div");
687 this.fileElement.className = "network-file";
688 this.fileElement.innerHTML = WebInspector.linkifyURL(resource.url, resource.displayName);
689 this.titleElement.appendChild(this.fileElement);
691 this.tipButtonElement = document.createElement("button");
692 this.tipButtonElement.className = "tip-button";
693 this.showingTipButton = this.resource.tips.length;
694 this.fileElement.insertBefore(this.tipButtonElement, this.fileElement.firstChild);
696 this.tipButtonElement.addEventListener("click", this.toggleTipBalloon.bind(this), false );
698 this.areaElement = document.createElement("div");
699 this.areaElement.className = "network-area";
700 this.titleElement.appendChild(this.areaElement);
702 this.barElement = document.createElement("div");
703 this.areaElement.appendChild(this.barElement);
705 this.infoElement = document.createElement("div");
706 this.infoElement.className = "network-info hidden";
707 this.resourceElement.appendChild(this.infoElement);
710 WebInspector.NetworkTimelineEntry.prototype = {
711 refresh: function(skipBoundryUpdate, skipTimelineSort, immediate)
713 if (!this.panel.visible) {
714 this.needsRefresh = true;
715 this.panel.needsRefresh = true;
719 delete this.needsRefresh;
721 if (!skipBoundryUpdate) {
722 if (this.panel.updateTimelineBoundriesIfNeeded(this.resource, immediate))
723 return; // updateTimelineBoundriesIfNeeded calls refresh() on all entries, so we can just return
726 if (!skipTimelineSort) {
728 this.panel.sortTimelineEntriesIfNeeded();
730 this.panel.sortTimelineEntriesSoonIfNeeded();
733 if (this.resource.startTime !== -1) {
734 var percentStart = ((this.resource.startTime - this.panel.earliestStartTime) / this.panel.totalDuration) * 100;
735 this.barElement.style.left = percentStart + "%";
737 this.barElement.style.left = null;
740 if (this.resource.endTime !== -1) {
741 var percentEnd = ((this.panel.latestEndTime - this.resource.endTime) / this.panel.totalDuration) * 100;
742 this.barElement.style.right = percentEnd + "%";
744 this.barElement.style.right = "0px";
747 this.barElement.className = "network-bar network-category-" + this.resource.category.name;
749 if (this.infoNeedsRefresh)
753 refreshInfo: function()
755 if (!this.showingInfo) {
756 this.infoNeedsRefresh = true;
760 if (!this.panel.visible) {
761 this.panel.needsRefresh = true;
762 this.infoNeedsRefresh = true;
766 this.infoNeedsRefresh = false;
768 this.infoElement.removeChildren();
771 {title: WebInspector.UIString("Request"), info: this.resource.sortedRequestHeaders},
772 {title: WebInspector.UIString("Response"), info: this.resource.sortedResponseHeaders}
775 function createSectionTable(section)
777 if (!section.info.length)
780 var table = document.createElement("table");
781 this.infoElement.appendChild(table);
783 var heading = document.createElement("th");
784 heading.textContent = section.title;
786 var row = table.createTHead().insertRow(-1).appendChild(heading);
787 var body = document.createElement("tbody");
788 table.appendChild(body);
790 section.info.forEach(function(header) {
791 var row = body.insertRow(-1);
792 var th = document.createElement("th");
793 th.textContent = header.header;
795 row.insertCell(-1).textContent = header.value;
799 sections.forEach(createSectionTable, this);
802 refreshInfoIfNeeded: function()
804 if (this.infoNeedsRefresh === false)
810 toggleShowingInfo: function()
812 this.showingInfo = !this.showingInfo;
817 return this._showingInfo;
822 if (this._showingInfo === x)
825 this._showingInfo = x;
827 var element = this.infoElement;
829 element.removeStyleClass("hidden");
830 element.style.setProperty("overflow", "hidden");
831 this.refreshInfoIfNeeded();
832 WebInspector.animateStyle([{element: element, start: {height: 0}, end: {height: element.offsetHeight}}], 250, function() { element.style.removeProperty("height"); element.style.removeProperty("overflow") });
834 element.style.setProperty("overflow", "hidden");
835 WebInspector.animateStyle([{element: element, end: {height: 0}}], 250, function() { element.addStyleClass("hidden"); element.style.removeProperty("height") });
839 get showingTipButton()
841 return !this.tipButtonElement.hasStyleClass("hidden");
844 set showingTipButton(x)
847 this.tipButtonElement.removeStyleClass("hidden");
849 this.tipButtonElement.addStyleClass("hidden");
852 toggleTipBalloon: function(event)
854 this.showingTipBalloon = !this.showingTipBalloon;
855 event.stopPropagation();
858 get showingTipBalloon()
860 return this._showingTipBalloon;
863 set showingTipBalloon(x)
865 if (this._showingTipBalloon === x)
868 this._showingTipBalloon = x;
871 if (!this.tipBalloonElement) {
872 this.tipBalloonElement = document.createElement("div");
873 this.tipBalloonElement.className = "tip-balloon";
874 this.titleElement.appendChild(this.tipBalloonElement);
876 this.tipBalloonContentElement = document.createElement("div");
877 this.tipBalloonContentElement.className = "tip-balloon-content";
878 this.tipBalloonElement.appendChild(this.tipBalloonContentElement);
880 for (var id in this.resource.tips)
881 tipText += this.resource.tips[id].message + "\n";
882 this.tipBalloonContentElement.textContent = tipText;
885 this.tipBalloonElement.removeStyleClass("hidden");
886 WebInspector.animateStyle([{element: this.tipBalloonElement, start: {left: 160, opacity: 0}, end: {left: 145, opacity: 1}}], 250);
888 var element = this.tipBalloonElement;
889 WebInspector.animateStyle([{element: this.tipBalloonElement, start: {left: 145, opacity: 1}, end: {left: 160, opacity: 0}}], 250, function() { element.addStyleClass("hidden") });
894 WebInspector.TimelineValueCalculator = function()
898 WebInspector.TimelineValueCalculator.prototype = {
899 computeValues: function(entries)
902 var categoryValues = {};
904 function compute(entry)
906 var value = this._value(entry);
907 if (value === undefined)
910 if (!(entry.resource.category.name in categoryValues))
911 categoryValues[entry.resource.category.name] = 0;
912 categoryValues[entry.resource.category.name] += value;
915 entries.forEach(compute, this);
917 return {categoryValues: categoryValues, total: total};
920 _value: function(entry)
930 formatValue: function(value)
932 return value.toString();
936 WebInspector.TransferTimeCalculator = function()
938 WebInspector.TimelineValueCalculator.call(this);
941 WebInspector.TransferTimeCalculator.prototype = {
942 computeValues: function(entries)
944 var entriesByCategory = {};
945 entries.forEach(function(entry) {
946 if (!(entry.resource.category.name in entriesByCategory))
947 entriesByCategory[entry.resource.category.name] = [];
948 entriesByCategory[entry.resource.category.name].push(entry);
953 var categoryValues = {};
954 for (var category in entriesByCategory) {
955 entriesByCategory[category].sort(WebInspector.NetworkPanel.timelineEntryCompare);
956 categoryValues[category] = 0;
958 var segment = {start: -1, end: -1};
959 entriesByCategory[category].forEach(function(entry) {
960 if (entry.resource.startTime == -1 || entry.resource.endTime == -1)
963 if (earliestStart === undefined)
964 earliestStart = entry.resource.startTime;
966 earliestStart = Math.min(earliestStart, entry.resource.startTime);
968 if (latestEnd === undefined)
969 latestEnd = entry.resource.endTime;
971 latestEnd = Math.max(latestEnd, entry.resource.endTime);
973 if (entry.resource.startTime <= segment.end) {
974 segment.end = Math.max(segment.end, entry.resource.endTime);
978 categoryValues[category] += segment.end - segment.start;
980 segment.start = entry.resource.startTime;
981 segment.end = entry.resource.endTime;
984 // Add the last segment
985 categoryValues[category] += segment.end - segment.start;
988 return {categoryValues: categoryValues, total: latestEnd - earliestStart};
993 return WebInspector.UIString("Transfer Time");
998 return WebInspector.UIString("Total Time");
1001 formatValue: function(value)
1003 return Number.secondsToString(value);
1007 WebInspector.TransferTimeCalculator.prototype.__proto__ = WebInspector.TimelineValueCalculator.prototype;
1009 WebInspector.TransferSizeCalculator = function()
1011 WebInspector.TimelineValueCalculator.call(this);
1014 WebInspector.TransferSizeCalculator.prototype = {
1015 _value: function(entry)
1017 return entry.resource.contentLength;
1022 return WebInspector.UIString("Transfer Size");
1027 return WebInspector.UIString("Total Size");
1030 formatValue: function(value)
1032 return Number.bytesToString(value);
1036 WebInspector.TransferSizeCalculator.prototype.__proto__ = WebInspector.TimelineValueCalculator.prototype;