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