43b9c88a806901acb514b58168b432eb82412780
[WebKit-https.git] / WebCore / page / inspector / ResourcesPanel.js
1 /*
2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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. 
16  *
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.
27  */
28
29 WebInspector.ResourcesPanel = function()
30 {
31     WebInspector.Panel.call(this);
32
33     this.element.addStyleClass("resources");
34
35     this.resourceViews = document.createElement("div");
36     this.resourceViews.id = "resource-views";
37     this.element.appendChild(this.resourceViews);
38
39     this.containerElement = document.createElement("div");
40     this.containerElement.id = "resources-container";
41     this.containerElement.addEventListener("scroll", this._updateDividersLabelBarPosition.bind(this), false);
42     this.element.appendChild(this.containerElement);
43
44     this.sidebarElement = document.createElement("div");
45     this.sidebarElement.id = "resources-sidebar";
46     this.sidebarElement.className = "sidebar";
47     this.containerElement.appendChild(this.sidebarElement);
48
49     this.sidebarResizeElement = document.createElement("div");
50     this.sidebarResizeElement.className = "sidebar-resizer-vertical";
51     this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
52     this.element.appendChild(this.sidebarResizeElement);
53
54     this.containerContentElement = document.createElement("div");
55     this.containerContentElement.id = "resources-container-content";
56     this.containerElement.appendChild(this.containerContentElement);
57
58     this.summaryElement = document.createElement("div");
59     this.summaryElement.id = "resources-summary";
60     this.containerContentElement.appendChild(this.summaryElement);
61
62     this.dividersElement = document.createElement("div");
63     this.dividersElement.id = "resources-dividers";
64     this.containerContentElement.appendChild(this.dividersElement);
65
66     this.dividersLabelBarElement = document.createElement("div");
67     this.dividersLabelBarElement.id = "resources-dividers-label-bar";
68     this.containerContentElement.appendChild(this.dividersLabelBarElement);
69
70     this.summaryGraphElement = document.createElement("canvas");
71     this.summaryGraphElement.setAttribute("width", "450");
72     this.summaryGraphElement.setAttribute("height", "38");
73     this.summaryGraphElement.id = "resources-summary-graph";
74     this.summaryElement.appendChild(this.summaryGraphElement);
75
76     this.legendElement = document.createElement("div");
77     this.legendElement.id = "resources-graph-legend";
78     this.summaryElement.appendChild(this.legendElement);
79
80     this.sidebarTreeElement = document.createElement("ol");
81     this.sidebarTreeElement.className = "sidebar-tree";
82     this.sidebarElement.appendChild(this.sidebarTreeElement);
83
84     this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
85
86     var timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time"));
87     timeGraphItem.calculator = new WebInspector.ResourceTransferTimeCalculator();
88     timeGraphItem.onselect = this._graphSelected.bind(this);
89
90     var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size"));
91     sizeGraphItem.calculator = new WebInspector.ResourceTransferSizeCalculator();
92     sizeGraphItem.onselect = this._graphSelected.bind(this);
93
94     this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true);
95     this.sidebarTree.appendChild(this.graphsTreeElement);
96
97     this.graphsTreeElement.appendChild(timeGraphItem);
98     this.graphsTreeElement.appendChild(sizeGraphItem);
99     this.graphsTreeElement.expand();
100
101     this.resourcesTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true);
102     this.sidebarTree.appendChild(this.resourcesTreeElement);
103
104     this.resourcesTreeElement.expand();
105
106     this.sortingFunction = WebInspector.ResourceSidebarTreeElement.CompareByTime;
107
108     this.reset();
109
110     timeGraphItem.select();
111 }
112
113 WebInspector.ResourcesPanel.prototype = {
114     toolbarItemClass: "resources",
115
116     get toolbarItemLabel()
117     {
118         return WebInspector.UIString("Resources");
119     },
120
121     show: function()
122     {
123         WebInspector.Panel.prototype.show.call(this);
124         this._updateDividersLabelBarPosition();
125         this._updateGraphBars();
126         this._updateSidebarWidth();
127         this.refreshIfNeeded();
128     },
129
130     resize: function()
131     {
132         this._updateGraphDividersIfNeeded();
133         this._updateGraphBars();
134
135         var visibleResourceView = this.visibleResourceView;
136         if (visibleResourceView && "resize" in visibleResourceView)
137             visibleResourceView.resize();
138     },
139
140     get visibleResourceView()
141     {
142         if (this.visibleResource)
143             return this.visibleResource._resourcesView;
144         return null;
145     },
146
147     get calculator()
148     {
149         return this._calculator;
150     },
151
152     set calculator(x)
153     {
154         this._calculator = x;
155         if (this._calculator)
156             this._calculator.reset();
157         this._refreshAllResources(false, true, true);
158         this._updateGraphDividersIfNeeded(true);
159         this._updateSummaryGraph();
160     },
161
162     get sortingFunction()
163     {
164         return this._sortingFunction;
165     },
166
167     set sortingFunction(x)
168     {
169         this._sortingFunction = x;
170         this._sortResourcesIfNeeded();
171     },
172
173     get needsRefresh() 
174     { 
175         return this._needsRefresh; 
176     }, 
177
178     set needsRefresh(x) 
179     { 
180         if (this._needsRefresh === x) 
181             return; 
182         this._needsRefresh = x; 
183         if (x && this.visible) 
184             this.refresh(); 
185     },
186
187     refreshIfNeeded: function() 
188     { 
189         if (this.needsRefresh) 
190             this.refresh(); 
191     },
192
193     refresh: function()
194     {
195         this.needsRefresh = false;
196
197         var staleResourcesLength = this._staleResources.length;
198         for (var i = 0; i < staleResourcesLength; ++i)
199             this.refreshResource(this._staleResources[i], false, true, true);
200
201         this._staleResources = [];
202
203         this._updateGraphDividersIfNeeded();
204         this._sortResourcesIfNeeded();
205         this._updateSummaryGraph();
206     },
207
208     reset: function()
209     {
210         if (this._calculator)
211             this._calculator.reset();
212
213         this._resources = [];
214         this._staleResources = [];
215
216         this.resourcesTreeElement.removeChildren();
217
218         this._updateGraphDividersIfNeeded();
219
220         this._drawSummaryGraph(); // draws an empty graph
221     },
222
223     addResource: function(resource)
224     {
225         this._resources.push(resource);
226
227         var resourceTreeElement = new WebInspector.ResourceSidebarTreeElement(resource);
228         resource._resourcesTreeElement = resourceTreeElement;
229
230         this.resourcesTreeElement.appendChild(resourceTreeElement);
231
232         this.refreshResource(resource);
233     },
234
235     refreshResource: function(resource, skipBoundaryUpdate, skipSort, immediate)
236     {
237         if (!this.visible) {
238             this._staleResources.push(resource);
239             this.needsRefresh = true;
240             return;
241         }
242
243         if (!skipBoundaryUpdate) {
244             if (this._updateGraphBoundriesIfNeeded(resource, immediate))
245                 return; // _updateGraphBoundriesIfNeeded refreshes all resources if it returns true, so just return.
246         }
247
248         if (!skipSort) {
249             if (immediate)
250                 this._sortResourcesIfNeeded();
251             else
252                 this._sortResourcesSoonIfNeeded();
253         }
254
255         this._updateSummaryGraphSoon();
256
257         if (!resource._resourcesTreeElement)
258             return;
259
260         resource._resourcesTreeElement.refresh();
261
262         var percentages = this.calculator.computeBarGraphPercentages(resource);
263
264         var barElement = resource._resourcesTreeElement.barElement;
265         barElement.style.left = percentages.start + "%";
266         barElement.style.right = (100 - percentages.end) + "%";
267     },
268
269     showResource: function(resource)
270     {
271         if (!resource)
272             return;
273
274         this.containerElement.addStyleClass("viewing-resource");
275
276         if (this.visibleResource && this.visibleResource._resourcesView)
277             this.visibleResource._resourcesView.hide();
278
279         if (!resource._resourcesView)
280             resource._resourcesView = this._createResourceView(resource);
281         resource._resourcesView.show();
282
283         this.visibleResource = resource;
284
285         this._updateSidebarWidth();
286     },
287
288     closeVisibleResource: function()
289     {
290         this.containerElement.removeStyleClass("viewing-resource");
291         this._updateDividersLabelBarPosition();
292         if (this.visibleResource && this.visibleResource._resourcesView)
293             this.visibleResource._resourcesView.hide();
294         delete this.visibleResource;
295
296         this._updateSidebarWidth();
297     },
298
299     handleKeyEvent: function(event)
300     {
301         this.sidebarTree.handleKeyEvent(event);
302     },
303
304     _makeLegendElement: function(label, value, color)
305     {
306         var legendElement = document.createElement("label");
307         legendElement.className = "resources-graph-legend-item";
308
309         if (color) {
310             var swatch = document.createElement("canvas");
311             swatch.className = "resources-graph-legend-swatch";
312             swatch.setAttribute("width", "13");
313             swatch.setAttribute("height", "24");
314
315             legendElement.appendChild(swatch);
316
317             this._drawSwatch(swatch, color);
318         }
319
320         var labelElement = document.createElement("div");
321         labelElement.className = "resources-graph-legend-label";
322         legendElement.appendChild(labelElement);
323
324         var headerElement = document.createElement("div");
325         var headerElement = document.createElement("div");
326         headerElement.className = "resources-graph-legend-header";
327         headerElement.textContent = label;
328         labelElement.appendChild(headerElement);
329
330         var valueElement = document.createElement("div");
331         valueElement.className = "resources-graph-legend-value";
332         valueElement.textContent = value;
333         labelElement.appendChild(valueElement);
334
335         return legendElement;
336     },
337
338     _sortResourcesSoonIfNeeded: function()
339     {
340         if ("_sortResourcesTimeout" in this)
341             return;
342         this._sortResourcesTimeout = setTimeout(this._sortResourcesIfNeeded.bind(this), 500);
343     },
344
345     _sortResourcesIfNeeded: function()
346     {
347         if ("_sortResourcesTimeout" in this) {
348             clearTimeout(this._sortResourcesTimeout);
349             delete this._sortResourcesTimeout;
350         }
351
352         var sortedElements = [].concat(this.resourcesTreeElement.children);
353         sortedElements.sort(this.sortingFunction);
354
355         var sortedElementsLength = sortedElements.length;
356         for (var i = 0; i < sortedElementsLength; ++i) {
357             var treeElement = sortedElements[i];
358             if (treeElement === this.resourcesTreeElement.children[i])
359                 continue;
360             this.resourcesTreeElement.removeChild(treeElement);
361             this.resourcesTreeElement.insertChild(treeElement, i);
362         }
363     },
364
365     _updateGraphBoundriesIfNeeded: function(resource, immediate)
366     {
367         var didChange = this.calculator.updateBoundries(resource);
368
369         if (didChange) {
370             if (immediate) {
371                 this._refreshAllResources(true, true, immediate);
372                 this._updateGraphDividersIfNeeded();
373             } else {
374                 this._refreshAllResourcesSoon(true, true, immediate);
375                 this._updateGraphDividersSoonIfNeeded();
376             }
377         }
378
379         return didChange;
380     },
381
382     _updateGraphDividersSoonIfNeeded: function()
383     {
384         if ("_updateGraphDividersTimeout" in this)
385             return;
386         this._updateGraphDividersTimeout = setTimeout(this._updateGraphDividersIfNeeded.bind(this), 500);
387     },
388
389     _updateGraphDividersIfNeeded: function(force)
390     {
391         if ("_updateGraphDividersTimeout" in this) {
392             clearTimeout(this._updateGraphDividersTimeout);
393             delete this._updateGraphDividersTimeout;
394         }
395
396         if (!this.visible) {
397             this.needsRefresh = true;
398             return;
399         }
400
401         if (document.body.offsetWidth <= 0) {
402             // The stylesheet hasn't loaded yet, so we need to update later.
403             setTimeout(this._updateGraphDividersIfNeeded.bind(this), 0);
404             return;
405         }
406
407         var dividerCount = Math.round(this.dividersElement.offsetWidth / 64);
408         var slice = this.calculator.boundarySpan / dividerCount;
409         if (!force && this._currentDividerSlice === slice)
410             return;
411
412         this._currentDividerSlice = slice;
413
414         this.dividersElement.removeChildren();
415         this.dividersLabelBarElement.removeChildren();
416
417         for (var i = 1; i <= dividerCount; ++i) {
418             var divider = document.createElement("div");
419             divider.className = "resources-divider";
420             if (i === dividerCount)
421                 divider.addStyleClass("last");
422             divider.style.left = ((i / dividerCount) * 100) + "%";
423
424             this.dividersElement.appendChild(divider);
425         }
426
427         for (var i = 1; i <= dividerCount; ++i) {
428             var divider = document.createElement("div");
429             divider.className = "resources-divider";
430             if (i === dividerCount)
431                 divider.addStyleClass("last");
432             divider.style.left = ((i / dividerCount) * 100) + "%";
433
434             var label = document.createElement("div");
435             label.className = "resources-divider-label";
436             if (!isNaN(slice))
437                 label.textContent = this.calculator.formatValue(slice * i);
438             divider.appendChild(label);
439
440             this.dividersLabelBarElement.appendChild(divider);
441         }
442     },
443
444     _updateGraphBars: function()
445     {
446         if (!this.visible) {
447             this.needsRefresh = true;
448             return;
449         }
450
451         if (document.body.offsetWidth <= 0) {
452             // The stylesheet hasn't loaded yet, so we need to update later.
453             setTimeout(this._updateGraphBars.bind(this), 0);
454             return;
455         }
456
457         // Add one to account for the sidebar border width.
458         var dividersElementWidth = this.dividersElement.offsetWidth + 1;
459
460         var resourcesLength = this._resources.length;
461         for (var i = 0; i < resourcesLength; ++i) {
462             var resourceTreeItem = this._resources[i]._resourcesTreeElement;
463             if (!resourceTreeItem)
464                 continue;
465             resourceTreeItem.graphSideElement.style.right = -dividersElementWidth + "px";
466             resourceTreeItem.graphSideElement.style.width = dividersElementWidth + "px";
467         }
468     },
469
470     _refreshAllResourcesSoon: function(skipBoundaryUpdate, skipSort, immediate)
471     {
472         if ("_refreshAllResourcesTimeout" in this)
473             return;
474         this._refreshAllResourcesTimeout = setTimeout(this._refreshAllResources.bind(this), 500, skipBoundaryUpdate, skipSort, immediate);
475     },
476
477     _refreshAllResources: function(skipBoundaryUpdate, skipSort, immediate)
478     {
479         if ("_refreshAllResourcesTimeout" in this) {
480             clearTimeout(this._refreshAllResourcesTimeout);
481             delete this._refreshAllResourcesTimeout;
482         }
483
484         var resourcesLength = this._resources.length;
485         for (var i = 0; i < resourcesLength; ++i)
486             this.refreshResource(this._resources[i], skipBoundaryUpdate, skipSort, immediate);
487     },
488
489     _fadeOutRect: function(ctx, x, y, w, h, a1, a2)
490     {
491         ctx.save();
492
493         var gradient = ctx.createLinearGradient(x, y, x, y + h);
494         gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")");
495         gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")");
496         gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)");
497
498         ctx.globalCompositeOperation = "destination-out";
499
500         ctx.fillStyle = gradient;
501         ctx.fillRect(x, y, w, h);
502
503         ctx.restore();
504     },
505
506     _drawSwatch: function(canvas, color)
507     {
508         var ctx = canvas.getContext("2d");
509
510         function drawSwatchSquare() {
511             ctx.fillStyle = color;
512             ctx.fillRect(0, 0, 13, 13);
513
514             var gradient = ctx.createLinearGradient(0, 0, 13, 13);
515             gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)");
516             gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");
517
518             ctx.fillStyle = gradient;
519             ctx.fillRect(0, 0, 13, 13);
520
521             gradient = ctx.createLinearGradient(13, 13, 0, 0);
522             gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)");
523             gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)");
524
525             ctx.fillStyle = gradient;
526             ctx.fillRect(0, 0, 13, 13);
527
528             ctx.strokeStyle = "rgba(0, 0, 0, 0.6)";
529             ctx.strokeRect(0.5, 0.5, 12, 12);
530         }
531
532         ctx.clearRect(0, 0, 13, 24);
533
534         drawSwatchSquare();
535
536         ctx.save();
537
538         ctx.translate(0, 25);
539         ctx.scale(1, -1);
540
541         drawSwatchSquare();
542
543         ctx.restore();
544
545         this._fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0);
546     },
547
548     _drawSummaryGraph: function(segments)
549     {
550         if (!this.summaryGraphElement)
551             return;
552
553         if (!segments || !segments.length) {
554             segments = [{color: "white", value: 1}];
555             this._showingEmptySummaryGraph = true;
556         } else
557             delete this._showingEmptySummaryGraph;
558
559         // Calculate the total of all segments.
560         var total = 0;
561         for (var i = 0; i < segments.length; ++i)
562             total += segments[i].value;
563
564         // Calculate the percentage of each segment, rounded to the nearest percent.
565         var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) });
566
567         // Calculate the total percentage.
568         var percentTotal = 0;
569         for (var i = 0; i < percents.length; ++i)
570             percentTotal += percents[i];
571
572         // Make sure our percentage total is not greater-than 100, it can be greater
573         // if we rounded up for a few segments.
574         while (percentTotal > 100) {
575             for (var i = 0; i < percents.length && percentTotal > 100; ++i) {
576                 if (percents[i] > 1) {
577                     --percents[i];
578                     --percentTotal;
579                 }
580             }
581         }
582
583         // Make sure our percentage total is not less-than 100, it can be less
584         // if we rounded down for a few segments.
585         while (percentTotal < 100) {
586             for (var i = 0; i < percents.length && percentTotal < 100; ++i) {
587                 ++percents[i];
588                 ++percentTotal;
589             }
590         }
591
592         var ctx = this.summaryGraphElement.getContext("2d");
593
594         var x = 0;
595         var y = 0;
596         var w = 450;
597         var h = 19;
598         var r = (h / 2);
599
600         function drawPillShadow()
601         {
602             // This draws a line with a shadow that is offset away from the line. The line is stroked
603             // twice with different X shadow offsets to give more feathered edges. Later we erase the
604             // line with destination-out 100% transparent black, leaving only the shadow. This only
605             // works if nothing has been drawn into the canvas yet.
606
607             ctx.beginPath();
608             ctx.moveTo(x + 4, y + h - 3 - 0.5);
609             ctx.lineTo(x + w - 4, y + h - 3 - 0.5);
610             ctx.closePath();
611
612             ctx.save();
613
614             ctx.shadowBlur = 2;
615             ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
616             ctx.shadowOffsetX = 3;
617             ctx.shadowOffsetY = 5;
618
619             ctx.strokeStyle = "white";
620             ctx.lineWidth = 1;
621
622             ctx.stroke();
623
624             ctx.shadowOffsetX = -3;
625
626             ctx.stroke();
627
628             ctx.restore();
629
630             ctx.save();
631
632             ctx.globalCompositeOperation = "destination-out";
633             ctx.strokeStyle = "rgba(0, 0, 0, 1)";
634             ctx.lineWidth = 1;
635
636             ctx.stroke();
637
638             ctx.restore();
639         }
640
641         function drawPill()
642         {
643             // Make a rounded rect path.
644             ctx.beginPath();
645             ctx.moveTo(x, y + r);
646             ctx.lineTo(x, y + h - r);
647             ctx.quadraticCurveTo(x, y + h, x + r, y + h);
648             ctx.lineTo(x + w - r, y + h);
649             ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - r);
650             ctx.lineTo(x + w, y + r);
651             ctx.quadraticCurveTo(x + w, y, x + w - r, y);
652             ctx.lineTo(x + r, y);
653             ctx.quadraticCurveTo(x, y, x, y + r);
654             ctx.closePath();
655
656             // Clip to the rounded rect path.
657             ctx.save();
658             ctx.clip();
659
660             // Fill the segments with the associated color.
661             var previousSegmentsWidth = 0;
662             for (var i = 0; i < segments.length; ++i) {
663                 var segmentWidth = Math.round(w * percents[i] / 100);
664                 ctx.fillStyle = segments[i].color;
665                 ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h);
666                 previousSegmentsWidth += segmentWidth;
667             }
668
669             // Draw the segment divider lines.
670             ctx.lineWidth = 1;
671             for (var i = 1; i < 20; ++i) {
672                 ctx.beginPath();
673                 ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y);
674                 ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h);
675                 ctx.closePath();
676
677                 ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
678                 ctx.stroke();
679
680                 ctx.beginPath();
681                 ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y);
682                 ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h);
683                 ctx.closePath();
684
685                 ctx.strokeStyle = "rgba(255, 255, 255, 0.2)";
686                 ctx.stroke();
687             }
688
689             // Draw the pill shading.
690             var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5));
691             lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)");
692             lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)");
693             lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");
694
695             var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h);
696             darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)");
697             darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)");
698             darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)");
699
700             ctx.fillStyle = darkGradient;
701             ctx.fillRect(x, y, w, h);
702
703             ctx.fillStyle = lightGradient;
704             ctx.fillRect(x, y, w, h);
705
706             ctx.restore();
707         }
708
709         ctx.clearRect(x, y, w, (h * 2));
710
711         drawPillShadow();
712         drawPill();
713
714         ctx.save();
715
716         ctx.translate(0, (h * 2) + 1);
717         ctx.scale(1, -1);
718
719         drawPill();
720
721         ctx.restore();
722
723         this._fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0);
724     },
725
726     _updateSummaryGraphSoon: function()
727     {
728         if ("_updateSummaryGraphTimeout" in this)
729             return;
730         this._updateSummaryGraphTimeout = setTimeout(this._updateSummaryGraph.bind(this), (this._showingEmptySummaryGraph ? 0 : 500));
731     },
732
733     _updateSummaryGraph: function()
734     {
735         if ("_updateSummaryGraphTimeout" in this) {
736             clearTimeout(this._updateSummaryGraphTimeout);
737             delete this._updateSummaryGraphTimeout;
738         }
739
740         var graphInfo = this.calculator.computeSummaryValues(this._resources);
741
742         var categoryOrder = ["documents", "stylesheets", "images", "scripts", "fonts", "other"];
743         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}};
744         var fillSegments = [];
745
746         this.legendElement.removeChildren();
747
748         if (this.totalLegendLabel)
749             this.totalLegendLabel.parentNode.removeChild(this.totalLegendLabel);
750
751         for (var i = 0; i < categoryOrder.length; ++i) {
752             var category = categoryOrder[i];
753             var size = graphInfo.categoryValues[category];
754             if (!size)
755                 continue;
756
757             var color = categoryColors[category];
758             var colorString = "rgb(" + color.r + ", " + color.g + ", " + color.b + ")";
759
760             var fillSegment = {color: colorString, value: size};
761             fillSegments.push(fillSegment);
762
763             var legendLabel = this._makeLegendElement(WebInspector.resourceCategories[category].title, this.calculator.formatValue(size), colorString);
764             this.legendElement.appendChild(legendLabel);
765         }
766
767         if (graphInfo.total) {
768             var totalLegendLabel = this._makeLegendElement(WebInspector.UIString("Total"), this.calculator.formatValue(graphInfo.total));
769             totalLegendLabel.addStyleClass("total");
770             this.legendElement.appendChild(totalLegendLabel);
771         }
772
773         this._drawSummaryGraph(fillSegments);
774     },
775
776     _updateDividersLabelBarPosition: function()
777     {
778         var scrollTop = this.containerElement.scrollTop;
779         var dividersTop = (scrollTop < this.summaryElement.offsetHeight ? this.summaryElement.offsetHeight : scrollTop);
780         this.dividersElement.style.top = scrollTop + "px";
781         this.dividersLabelBarElement.style.top = dividersTop + "px";
782     },
783
784     _graphSelected: function(treeElement)
785     {
786         this.closeVisibleResource();
787         this.calculator = treeElement.calculator;
788         this.containerElement.scrollTop = 0;
789     },
790
791     _createResourceView: function(resource)
792     {
793         if (resource.finished && !resource.failed) {
794             switch (resource.category) {
795             case WebInspector.resourceCategories.documents:
796             case WebInspector.resourceCategories.stylesheets:
797             case WebInspector.resourceCategories.scripts:
798                 return new WebInspector.SourceView(resource);
799             case WebInspector.resourceCategories.images:
800                 return new WebInspector.ImageView(resource);
801             case WebInspector.resourceCategories.fonts:
802                 return new WebInspector.FontView(resource);
803             }
804         }
805
806         return new WebInspector.ResourceView(resource);
807     },
808
809     _startSidebarDragging: function(event)
810     {
811         WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
812     },
813
814     _sidebarDragging: function(event)
815     {
816         this._updateSidebarWidth(event.pageX);
817
818         event.preventDefault();
819     },
820
821     _endSidebarDragging: function(event)
822     {
823         WebInspector.elementDragEnd(event);
824     },
825
826     _updateSidebarWidth: function(width)
827     {
828         if (this.sidebarElement.offsetWidth <= 0) {
829             // The stylesheet hasn't loaded yet, so we need to update later.
830             setTimeout(this._updateSidebarWidth.bind(this), 0, width);
831             return;
832         }
833
834         if (!("_currentSidebarWidth" in this))
835             this._currentSidebarWidth = this.sidebarElement.offsetWidth;
836
837         if (typeof width === "undefined")
838             width = this._currentSidebarWidth;
839
840         width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
841
842         this._currentSidebarWidth = width;
843
844         if (this.visibleResource) {
845             this.containerElement.style.width = width + "px";
846             this.sidebarElement.style.removeProperty("width");
847         } else {
848             this.sidebarElement.style.width = width + "px";
849             this.containerElement.style.removeProperty("width");
850         }
851
852         this.containerContentElement.style.left = width + "px";
853         this.resourceViews.style.left = width + "px";
854         this.sidebarResizeElement.style.left = (width - 3) + "px";
855
856         this._updateGraphBars();
857         this._updateGraphDividersIfNeeded();
858     }
859 }
860
861 WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
862
863 WebInspector.ResourceCalculator = function()
864 {
865 }
866
867 WebInspector.ResourceCalculator.prototype = {
868     computeSummaryValues: function(resources)
869     {
870         var total = 0;
871         var categoryValues = {};
872
873         var resourcesLength = resources.length;
874         for (var i = 0; i < resourcesLength; ++i) {
875             var resource = resources[i];
876             var value = this._value(resource);
877             if (typeof value === "undefined")
878                 continue;
879             if (!(resource.category.name in categoryValues))
880                 categoryValues[resource.category.name] = 0;
881             categoryValues[resource.category.name] += value;
882             total += value;
883         }
884
885         return {categoryValues: categoryValues, total: total};
886     },
887
888     computeBarGraphPercentages: function(resource)
889     {
890         return {start: 0, end: (this._value(resource) / this.boundarySpan) * 100};
891     },
892
893     get boundarySpan()
894     {
895         return this.maximumBoundary - this.minimumBoundary;
896     },
897
898     updateBoundries: function(resource)
899     {
900         this.minimumBoundary = 0;
901
902         var value = this._value(resource);
903         if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
904             this.maximumBoundary = value;
905             return true;
906         }
907
908         return false;
909     },
910
911     reset: function()
912     {
913         delete this.minimumBoundary;
914         delete this.maximumBoundary;
915     },
916
917     _value: function(resource)
918     {
919         return 0;
920     },
921
922     formatValue: function(value)
923     {
924         return value.toString();
925     }
926 }
927
928 WebInspector.ResourceTransferTimeCalculator = function()
929 {
930     WebInspector.ResourceCalculator.call(this);
931 }
932
933 WebInspector.ResourceTransferTimeCalculator.prototype = {
934     computeSummaryValues: function(resources)
935     {
936         var resourcesByCategory = {};
937         var resourcesLength = resources.length;
938         for (var i = 0; i < resourcesLength; ++i) {
939             var resource = resources[i];
940             if (!(resource.category.name in resourcesByCategory))
941                 resourcesByCategory[resource.category.name] = [];
942             resourcesByCategory[resource.category.name].push(resource);
943         }
944
945         var earliestStart;
946         var latestEnd;
947         var categoryValues = {};
948         for (var category in resourcesByCategory) {
949             resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
950             categoryValues[category] = 0;
951
952             var segment = {start: -1, end: -1};
953
954             var categoryResources = resourcesByCategory[category];
955             var resourcesLength = categoryResources.length;
956             for (var i = 0; i < resourcesLength; ++i) {
957                 var resource = categoryResources[i];
958                 if (resource.startTime === -1 || resource.endTime === -1)
959                     continue;
960
961                 if (typeof earliestStart === "undefined")
962                     earliestStart = resource.startTime;
963                 else
964                     earliestStart = Math.min(earliestStart, resource.startTime);
965
966                 if (typeof latestEnd === "undefined")
967                     latestEnd = resource.endTime;
968                 else
969                     latestEnd = Math.max(latestEnd, resource.endTime);
970
971                 if (resource.startTime <= segment.end) {
972                     segment.end = Math.max(segment.end, resource.endTime);
973                     continue;
974                 }
975
976                 categoryValues[category] += segment.end - segment.start;
977
978                 segment.start = resource.startTime;
979                 segment.end = resource.endTime;
980             }
981
982             // Add the last segment
983             categoryValues[category] += segment.end - segment.start;
984         }
985
986         return {categoryValues: categoryValues, total: latestEnd - earliestStart};
987     },
988
989     computeBarGraphPercentages: function(resource)
990     {
991         if (resource.startTime !== -1)
992             var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
993         else
994             var start = 100;
995
996         if (resource.endTime !== -1)
997             var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
998         else
999             var end = 100;
1000
1001         return {start: start, end: end};
1002     },
1003
1004     updateBoundries: function(resource)
1005     {
1006         var didChange = false;
1007         if (resource.startTime !== -1 && (typeof this.minimumBoundary === "undefined" || resource.startTime < this.minimumBoundary)) {
1008             this.minimumBoundary = resource.startTime;
1009             didChange = true;
1010         }
1011
1012         if (resource.endTime !== -1 && (typeof this.maximumBoundary === "undefined" || resource.endTime > this.maximumBoundary)) {
1013             this.maximumBoundary = resource.endTime;
1014             didChange = true;
1015         }
1016
1017         return didChange;
1018     },
1019
1020     formatValue: function(value)
1021     {
1022         return Number.secondsToString(value);
1023     }
1024 }
1025
1026 WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype;
1027
1028 WebInspector.ResourceTransferSizeCalculator = function()
1029 {
1030     WebInspector.ResourceCalculator.call(this);
1031 }
1032
1033 WebInspector.ResourceTransferSizeCalculator.prototype = {
1034     _value: function(resource)
1035     {
1036         return resource.contentLength;
1037     },
1038
1039     formatValue: function(value)
1040     {
1041         return Number.bytesToString(value);
1042     }
1043 }
1044
1045 WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype;
1046
1047 WebInspector.ResourceSidebarTreeElement = function(resource)
1048 {
1049     this.resource = resource;
1050
1051     WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource);
1052
1053     this.refreshTitles();
1054
1055     this.graphSideElement = document.createElement("div");
1056     this.graphSideElement.className = "resources-graph-side";
1057
1058     this.barAreaElement = document.createElement("div");
1059     this.barAreaElement.className = "resources-graph-bar-area";
1060     this.graphSideElement.appendChild(this.barAreaElement);
1061
1062     this.barElement = document.createElement("div");
1063     this.barElement.className = "resources-graph-bar";
1064     this.barAreaElement.appendChild(this.barElement);
1065 }
1066
1067 WebInspector.ResourceSidebarTreeElement.prototype = {
1068     onattach: function()
1069     {
1070         WebInspector.SidebarTreeElement.prototype.onattach.call(this);
1071
1072         this._listItemNode.appendChild(this.graphSideElement);
1073         this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
1074     },
1075
1076     onselect: function()
1077     {
1078         WebInspector.panels.resources.showResource(this.resource);
1079     },
1080
1081     get mainTitle()
1082     {
1083         return this.resource.displayName;
1084     },
1085
1086     set mainTitle(x)
1087     {
1088         // Do nothing.
1089     },
1090
1091     get subtitle()
1092     {
1093         var subtitle = this.resource.displayDomain;
1094
1095         if (this.resource.path && this.resource.lastPathComponent) {
1096             var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent);
1097             if (lastPathComponentIndex != -1)
1098                 subtitle += this.resource.path.substring(0, lastPathComponentIndex);
1099         }
1100
1101         return subtitle;
1102     },
1103
1104     set subtitle(x)
1105     {
1106         // Do nothing.
1107     },
1108
1109     refresh: function()
1110     {
1111         this.refreshTitles();
1112
1113         var newClassName = "sidebar-tree-item resource-sidebar-tree-item resources-category-" + this.resource.category.name;
1114         if (this._listItemNode && this._listItemNode.className !== newClassName)
1115             this._listItemNode.className = newClassName;
1116     }
1117 }
1118
1119 WebInspector.ResourceSidebarTreeElement.CompareByTime = function(a, b)
1120 {
1121     return WebInspector.Resource.CompareByTime(a.resource, b.resource);
1122 }
1123
1124 WebInspector.ResourceSidebarTreeElement.CompareBySize = function(a, b)
1125 {
1126     return WebInspector.Resource.CompareBySize(a.resource, b.resource);
1127 }
1128
1129 WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b)
1130 {
1131     return WebInspector.Resource.CompareByDescendingSize(a.resource, b.resource);
1132 }
1133
1134 WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;