Fixes a bug where the Inspector could have 0ms timers firing
[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.onselect = this._graphSelected.bind(this);
88
89     var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator();
90     var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator();
91
92     timeGraphItem.sortingOptions = [
93         { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator },
94         { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator },
95         { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator },
96         { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator },
97         { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator },
98     ];
99
100     timeGraphItem.selectedSortingOptionIndex = 1;
101
102     var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size"));
103     sizeGraphItem.onselect = this._graphSelected.bind(this);
104
105     var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator();
106     sizeGraphItem.sortingOptions = [
107         { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator },
108     ];
109
110     sizeGraphItem.selectedSortingOptionIndex = 0;
111
112     this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true);
113     this.sidebarTree.appendChild(this.graphsTreeElement);
114
115     this.graphsTreeElement.appendChild(timeGraphItem);
116     this.graphsTreeElement.appendChild(sizeGraphItem);
117     this.graphsTreeElement.expand();
118
119     this.resourcesTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true);
120     this.sidebarTree.appendChild(this.resourcesTreeElement);
121
122     this.resourcesTreeElement.expand();
123
124     this.largerResourcesButton = document.createElement("button");
125     this.largerResourcesButton.id = "resources-larger-resources-status-bar-item";
126     this.largerResourcesButton.className = "status-bar-item toggled-on";
127     this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
128     this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
129
130     this.sortingSelectElement = document.createElement("select");
131     this.sortingSelectElement.className = "status-bar-item";
132     this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false);
133
134     this.reset();
135
136     timeGraphItem.select();
137 }
138
139 WebInspector.ResourcesPanel.prototype = {
140     toolbarItemClass: "resources",
141
142     get toolbarItemLabel()
143     {
144         return WebInspector.UIString("Resources");
145     },
146
147     get statusBarItems()
148     {
149         return [this.largerResourcesButton, this.sortingSelectElement];
150     },
151
152     show: function()
153     {
154         WebInspector.Panel.prototype.show.call(this);
155         this._updateDividersLabelBarPosition();
156         this._updateSidebarWidth();
157         this.refreshIfNeeded();
158
159         var visibleResourceView = this.visibleResourceView;
160         if (visibleResourceView) {
161             visibleResourceView.headersVisible = true;
162             visibleResourceView.show(this.resourceViews);
163         }
164     },
165
166     resize: function()
167     {
168         this._updateGraphDividersIfNeeded();
169         this._updateGraphBars();
170
171         var visibleResourceView = this.visibleResourceView;
172         if (visibleResourceView && "resize" in visibleResourceView)
173             visibleResourceView.resize();
174     },
175
176     get visibleResourceView()
177     {
178         if (this.visibleResource)
179             return this.visibleResource._resourcesView;
180         return null;
181     },
182
183     get calculator()
184     {
185         return this._calculator;
186     },
187
188     set calculator(x)
189     {
190         if (this._calculator === x)
191             return;
192
193         this._calculator = x;
194         if (this._calculator)
195             this._calculator.reset();
196         this._refreshAllResources(false, true, true);
197         this._updateGraphDividersIfNeeded(true);
198         this._updateSummaryGraph();
199     },
200
201     get sortingFunction()
202     {
203         return this._sortingFunction;
204     },
205
206     set sortingFunction(x)
207     {
208         this._sortingFunction = x;
209         this._sortResourcesIfNeeded();
210     },
211
212     get needsRefresh() 
213     { 
214         return this._needsRefresh; 
215     }, 
216
217     set needsRefresh(x) 
218     { 
219         if (this._needsRefresh === x) 
220             return; 
221         this._needsRefresh = x; 
222         if (x && this.visible) 
223             this.refresh(); 
224     },
225
226     refreshIfNeeded: function() 
227     { 
228         if (this.needsRefresh) 
229             this.refresh(); 
230     },
231
232     refresh: function()
233     {
234         this.needsRefresh = false;
235
236         var staleResourcesLength = this._staleResources.length;
237         for (var i = 0; i < staleResourcesLength; ++i)
238             this.refreshResource(this._staleResources[i], false, true, true);
239
240         this._staleResources = [];
241
242         this._updateGraphDividersIfNeeded();
243         this._sortResourcesIfNeeded();
244         this._updateSummaryGraph();
245     },
246
247     reset: function()
248     {
249         this.closeVisibleResource();
250
251         if (this._calculator)
252             this._calculator.reset();
253
254         if (this._resources) {
255             var resourcesLength = this._resources.length;
256             for (var i = 0; i < resourcesLength; ++i) {
257                 var resource = this._resources[i];
258
259                 resource.warnings = 0;
260                 resource.errors = 0;
261
262                 delete resource._resourcesTreeElement;
263                 delete resource._resourcesView;
264             }
265         }
266
267         this._resources = [];
268         this._staleResources = [];
269
270         this.resourcesTreeElement.removeChildren();
271         this.resourceViews.removeChildren();
272
273         this._updateGraphDividersIfNeeded(true);
274
275         this._drawSummaryGraph(); // draws an empty graph
276     },
277
278     addResource: function(resource)
279     {
280         this._resources.push(resource);
281
282         var resourceTreeElement = new WebInspector.ResourceSidebarTreeElement(resource);
283         resource._resourcesTreeElement = resourceTreeElement;
284
285         resourceTreeElement.updateGraphSideWidth(this.dividersElement.offsetWidth);
286
287         this.resourcesTreeElement.appendChild(resourceTreeElement);
288
289         this.refreshResource(resource);
290     },
291
292     removeResource: function(resource)
293     {
294         if (this.visibleResourceView === resource._resourcesView)
295             this.closeVisibleResource();
296
297         var resourcesLength = this._resources.length;
298         for (var i = 0; i < resourcesLength; ++i) {
299             if (this._resources[i] === resource) {
300                 this._resources.splice(i, 1);
301                 break;
302             }
303         }
304
305         this.resourcesTreeElement.removeChild(resource._resourcesTreeElement);
306
307         resource.warnings = 0;
308         resource.errors = 0;
309
310         delete resource._resourcesTreeElement;
311         delete resource._resourcesView;
312     },
313
314     addMessageToResource: function(resource, msg)
315     {
316         if (!resource)
317             return;
318
319         switch (msg.level) {
320         case WebInspector.ConsoleMessage.MessageLevel.Warning:
321             ++resource.warnings;
322             break;
323         case WebInspector.ConsoleMessage.MessageLevel.Error:
324             ++resource.errors;
325             break;
326         }
327
328         resource._resourcesTreeElement.updateErrorsAndWarnings();
329
330         var view = this.resourceViewForResource(resource);
331         if (view.addMessage)
332             view.addMessage(msg);
333     },
334
335     clearMessages: function()
336     {
337         var resourcesLength = this._resources.length;
338         for (var i = 0; i < resourcesLength; ++i) {
339             var resource = this._resources[i];
340             resource.warnings = 0;
341             resource.errors = 0;
342
343             resource._resourcesTreeElement.updateErrorsAndWarnings();
344
345             var view = resource._resourcesView;
346             if (!view || !view.clearMessages)
347                 continue;
348             view.clearMessages();
349         }
350     },
351
352     refreshResource: function(resource, skipBoundaryUpdate, skipSort, immediate)
353     {
354         if (!this.visible) {
355             this._staleResources.push(resource);
356             this.needsRefresh = true;
357             return;
358         }
359
360         if (!skipBoundaryUpdate) {
361             if (this._updateGraphBoundriesIfNeeded(resource, immediate))
362                 return; // _updateGraphBoundriesIfNeeded refreshes all resources if it returns true, so just return.
363         }
364
365         if (!skipSort) {
366             if (immediate)
367                 this._sortResourcesIfNeeded();
368             else
369                 this._sortResourcesSoonIfNeeded();
370         }
371
372         this._updateSummaryGraphSoon();
373
374         if (!resource._resourcesTreeElement)
375             return;
376
377         resource._resourcesTreeElement.refresh();
378
379         var percentages = this.calculator.computeBarGraphPercentages(resource);
380
381         var barLeftElement = resource._resourcesTreeElement.barLeftElement;
382         barLeftElement.style.left = percentages.start + "%";
383         barLeftElement.style.right = (100 - percentages.end) + "%";
384
385         var barRightElement = resource._resourcesTreeElement.barRightElement;
386         barRightElement.style.left = percentages.middle + "%";
387         barRightElement.style.right = (100 - percentages.end) + "%";
388     },
389
390     recreateViewForResourceIfNeeded: function(resource)
391     {
392         if (!resource || !resource._resourcesView)
393             return;
394
395         var newView = this._createResourceView(resource);
396         if (newView.prototype === resource._resourcesView.prototype)
397             return;
398
399         resource.warnings = 0;
400         resource.errors = 0;
401
402         resource._resourcesTreeElement.updateErrorsAndWarnings();
403
404         var oldView = resource._resourcesView;
405
406         resource._resourcesView.detach();
407         delete resource._resourcesView;
408
409         resource._resourcesView = newView;
410
411         newView.headersVisible = oldView.headersVisible;
412
413         if (oldView.visible && oldView.element.parentNode)
414             newView.show(oldView.element.parentNode);
415     },
416
417     showResource: function(resource, line)
418     {
419         if (!resource)
420             return;
421
422         this.containerElement.addStyleClass("viewing-resource");
423
424         if (this.visibleResource && this.visibleResource._resourcesView)
425             this.visibleResource._resourcesView.hide();
426
427         var view = this.resourceViewForResource(resource);
428         view.headersVisible = true;
429         view.show(this.resourceViews);
430
431         if (line) {
432             if (view.revealLine)
433                 view.revealLine(line);
434             if (view.highlightLine)
435                 view.highlightLine(line);
436         }
437
438         if (resource._resourcesTreeElement) {
439             resource._resourcesTreeElement.reveal();
440             resource._resourcesTreeElement.select(true);
441         }
442
443         this.visibleResource = resource;
444
445         this._updateSidebarWidth();
446     },
447
448     closeVisibleResource: function()
449     {
450         this.containerElement.removeStyleClass("viewing-resource");
451         this._updateDividersLabelBarPosition();
452
453         if (this.visibleResource && this.visibleResource._resourcesView)
454             this.visibleResource._resourcesView.hide();
455         delete this.visibleResource;
456
457         if (this._lastSelectedGraphTreeElement)
458             this._lastSelectedGraphTreeElement.select(true);
459
460         this._updateSidebarWidth();
461     },
462
463     resourceViewForResource: function(resource)
464     {
465         if (!resource)
466             return null;
467         if (!resource._resourcesView)
468             resource._resourcesView = this._createResourceView(resource);
469         return resource._resourcesView;
470     },
471
472     sourceFrameForResource: function(resource)
473     {
474         var view = this.resourceViewForResource(resource);
475         if (!view)
476             return null;
477
478         if (!view.setupSourceFrameIfNeeded)
479             return null;
480
481         // Setting up the source frame requires that we be attached.
482         if (!this.element.parentNode)
483             this.attach();
484
485         view.setupSourceFrameIfNeeded();
486         return view.sourceFrame;
487     },
488
489     handleKeyEvent: function(event)
490     {
491         this.sidebarTree.handleKeyEvent(event);
492     },
493
494     _makeLegendElement: function(label, value, color)
495     {
496         var legendElement = document.createElement("label");
497         legendElement.className = "resources-graph-legend-item";
498
499         if (color) {
500             var swatch = document.createElement("canvas");
501             swatch.className = "resources-graph-legend-swatch";
502             swatch.setAttribute("width", "13");
503             swatch.setAttribute("height", "24");
504
505             legendElement.appendChild(swatch);
506
507             this._drawSwatch(swatch, color);
508         }
509
510         var labelElement = document.createElement("div");
511         labelElement.className = "resources-graph-legend-label";
512         legendElement.appendChild(labelElement);
513
514         var headerElement = document.createElement("div");
515         var headerElement = document.createElement("div");
516         headerElement.className = "resources-graph-legend-header";
517         headerElement.textContent = label;
518         labelElement.appendChild(headerElement);
519
520         var valueElement = document.createElement("div");
521         valueElement.className = "resources-graph-legend-value";
522         valueElement.textContent = value;
523         labelElement.appendChild(valueElement);
524
525         return legendElement;
526     },
527
528     _sortResourcesSoonIfNeeded: function()
529     {
530         if ("_sortResourcesTimeout" in this)
531             return;
532         this._sortResourcesTimeout = setTimeout(this._sortResourcesIfNeeded.bind(this), 500);
533     },
534
535     _sortResourcesIfNeeded: function()
536     {
537         if ("_sortResourcesTimeout" in this) {
538             clearTimeout(this._sortResourcesTimeout);
539             delete this._sortResourcesTimeout;
540         }
541
542         var sortedElements = [].concat(this.resourcesTreeElement.children);
543         sortedElements.sort(this.sortingFunction);
544
545         var sortedElementsLength = sortedElements.length;
546         for (var i = 0; i < sortedElementsLength; ++i) {
547             var treeElement = sortedElements[i];
548             if (treeElement === this.resourcesTreeElement.children[i])
549                 continue;
550             var wasSelected = treeElement.selected;
551             this.resourcesTreeElement.removeChild(treeElement);
552             this.resourcesTreeElement.insertChild(treeElement, i);
553             if (wasSelected)
554                 treeElement.select(true);
555         }
556     },
557
558     _updateGraphBoundriesIfNeeded: function(resource, immediate)
559     {
560         var didChange = this.calculator.updateBoundries(resource);
561
562         if (didChange) {
563             if (immediate) {
564                 this._refreshAllResources(true, true, immediate);
565                 this._updateGraphDividersIfNeeded();
566             } else {
567                 this._refreshAllResourcesSoon(true, true, immediate);
568                 this._updateGraphDividersSoonIfNeeded();
569             }
570         }
571
572         return didChange;
573     },
574
575     _updateGraphDividersSoonIfNeeded: function()
576     {
577         if ("_updateGraphDividersTimeout" in this)
578             return;
579         this._updateGraphDividersTimeout = setTimeout(this._updateGraphDividersIfNeeded.bind(this), 500);
580     },
581
582     _updateGraphDividersIfNeeded: function(force)
583     {
584         if ("_updateGraphDividersTimeout" in this) {
585             clearTimeout(this._updateGraphDividersTimeout);
586             delete this._updateGraphDividersTimeout;
587         }
588
589         if (!this.visible) {
590             this.needsRefresh = true;
591             return;
592         }
593
594         if (document.body.offsetWidth <= 0) {
595             // The stylesheet hasn't loaded yet or the window is closed,
596             // so we can't calculate what is need. Return early.
597             return;
598         }
599
600         var dividerCount = Math.round(this.dividersElement.offsetWidth / 64);
601         var slice = this.calculator.boundarySpan / dividerCount;
602         if (!force && this._currentDividerSlice === slice)
603             return;
604
605         this._currentDividerSlice = slice;
606
607         this.dividersElement.removeChildren();
608         this.dividersLabelBarElement.removeChildren();
609
610         for (var i = 1; i <= dividerCount; ++i) {
611             var divider = document.createElement("div");
612             divider.className = "resources-divider";
613             if (i === dividerCount)
614                 divider.addStyleClass("last");
615             divider.style.left = ((i / dividerCount) * 100) + "%";
616
617             this.dividersElement.appendChild(divider);
618         }
619
620         for (var i = 1; i <= dividerCount; ++i) {
621             var divider = document.createElement("div");
622             divider.className = "resources-divider";
623             if (i === dividerCount)
624                 divider.addStyleClass("last");
625             divider.style.left = ((i / dividerCount) * 100) + "%";
626
627             var label = document.createElement("div");
628             label.className = "resources-divider-label";
629             if (!isNaN(slice))
630                 label.textContent = this.calculator.formatValue(slice * i);
631             divider.appendChild(label);
632
633             this.dividersLabelBarElement.appendChild(divider);
634         }
635     },
636
637     _updateGraphBars: function()
638     {
639         if (!this.visible) {
640             this.needsRefresh = true;
641             return;
642         }
643
644         if (document.body.offsetWidth <= 0) {
645             // The stylesheet hasn't loaded yet or the window is closed,
646             // so we can't calculate what is need. Return early.
647             return;
648         }
649
650         var dividersElementWidth = this.dividersElement.offsetWidth;
651         var resourcesLength = this._resources.length;
652         for (var i = 0; i < resourcesLength; ++i) {
653             var resourceTreeItem = this._resources[i]._resourcesTreeElement;
654             if (!resourceTreeItem)
655                 continue;
656             resourceTreeItem.updateGraphSideWidth(dividersElementWidth);
657         }
658     },
659
660     _refreshAllResourcesSoon: function(skipBoundaryUpdate, skipSort, immediate)
661     {
662         if ("_refreshAllResourcesTimeout" in this)
663             return;
664         this._refreshAllResourcesTimeout = setTimeout(this._refreshAllResources.bind(this), 500, skipBoundaryUpdate, skipSort, immediate);
665     },
666
667     _refreshAllResources: function(skipBoundaryUpdate, skipSort, immediate)
668     {
669         if ("_refreshAllResourcesTimeout" in this) {
670             clearTimeout(this._refreshAllResourcesTimeout);
671             delete this._refreshAllResourcesTimeout;
672         }
673
674         var resourcesLength = this._resources.length;
675         for (var i = 0; i < resourcesLength; ++i)
676             this.refreshResource(this._resources[i], skipBoundaryUpdate, skipSort, immediate);
677     },
678
679     _fadeOutRect: function(ctx, x, y, w, h, a1, a2)
680     {
681         ctx.save();
682
683         var gradient = ctx.createLinearGradient(x, y, x, y + h);
684         gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")");
685         gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")");
686         gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)");
687
688         ctx.globalCompositeOperation = "destination-out";
689
690         ctx.fillStyle = gradient;
691         ctx.fillRect(x, y, w, h);
692
693         ctx.restore();
694     },
695
696     _drawSwatch: function(canvas, color)
697     {
698         var ctx = canvas.getContext("2d");
699
700         function drawSwatchSquare() {
701             ctx.fillStyle = color;
702             ctx.fillRect(0, 0, 13, 13);
703
704             var gradient = ctx.createLinearGradient(0, 0, 13, 13);
705             gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)");
706             gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");
707
708             ctx.fillStyle = gradient;
709             ctx.fillRect(0, 0, 13, 13);
710
711             gradient = ctx.createLinearGradient(13, 13, 0, 0);
712             gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)");
713             gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)");
714
715             ctx.fillStyle = gradient;
716             ctx.fillRect(0, 0, 13, 13);
717
718             ctx.strokeStyle = "rgba(0, 0, 0, 0.6)";
719             ctx.strokeRect(0.5, 0.5, 12, 12);
720         }
721
722         ctx.clearRect(0, 0, 13, 24);
723
724         drawSwatchSquare();
725
726         ctx.save();
727
728         ctx.translate(0, 25);
729         ctx.scale(1, -1);
730
731         drawSwatchSquare();
732
733         ctx.restore();
734
735         this._fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0);
736     },
737
738     _drawSummaryGraph: function(segments)
739     {
740         if (!this.summaryGraphElement)
741             return;
742
743         if (!segments || !segments.length) {
744             segments = [{color: "white", value: 1}];
745             this._showingEmptySummaryGraph = true;
746         } else
747             delete this._showingEmptySummaryGraph;
748
749         // Calculate the total of all segments.
750         var total = 0;
751         for (var i = 0; i < segments.length; ++i)
752             total += segments[i].value;
753
754         // Calculate the percentage of each segment, rounded to the nearest percent.
755         var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) });
756
757         // Calculate the total percentage.
758         var percentTotal = 0;
759         for (var i = 0; i < percents.length; ++i)
760             percentTotal += percents[i];
761
762         // Make sure our percentage total is not greater-than 100, it can be greater
763         // if we rounded up for a few segments.
764         while (percentTotal > 100) {
765             for (var i = 0; i < percents.length && percentTotal > 100; ++i) {
766                 if (percents[i] > 1) {
767                     --percents[i];
768                     --percentTotal;
769                 }
770             }
771         }
772
773         // Make sure our percentage total is not less-than 100, it can be less
774         // if we rounded down for a few segments.
775         while (percentTotal < 100) {
776             for (var i = 0; i < percents.length && percentTotal < 100; ++i) {
777                 ++percents[i];
778                 ++percentTotal;
779             }
780         }
781
782         var ctx = this.summaryGraphElement.getContext("2d");
783
784         var x = 0;
785         var y = 0;
786         var w = 450;
787         var h = 19;
788         var r = (h / 2);
789
790         function drawPillShadow()
791         {
792             // This draws a line with a shadow that is offset away from the line. The line is stroked
793             // twice with different X shadow offsets to give more feathered edges. Later we erase the
794             // line with destination-out 100% transparent black, leaving only the shadow. This only
795             // works if nothing has been drawn into the canvas yet.
796
797             ctx.beginPath();
798             ctx.moveTo(x + 4, y + h - 3 - 0.5);
799             ctx.lineTo(x + w - 4, y + h - 3 - 0.5);
800             ctx.closePath();
801
802             ctx.save();
803
804             ctx.shadowBlur = 2;
805             ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
806             ctx.shadowOffsetX = 3;
807             ctx.shadowOffsetY = 5;
808
809             ctx.strokeStyle = "white";
810             ctx.lineWidth = 1;
811
812             ctx.stroke();
813
814             ctx.shadowOffsetX = -3;
815
816             ctx.stroke();
817
818             ctx.restore();
819
820             ctx.save();
821
822             ctx.globalCompositeOperation = "destination-out";
823             ctx.strokeStyle = "rgba(0, 0, 0, 1)";
824             ctx.lineWidth = 1;
825
826             ctx.stroke();
827
828             ctx.restore();
829         }
830
831         function drawPill()
832         {
833             // Make a rounded rect path.
834             ctx.beginPath();
835             ctx.moveTo(x, y + r);
836             ctx.lineTo(x, y + h - r);
837             ctx.quadraticCurveTo(x, y + h, x + r, y + h);
838             ctx.lineTo(x + w - r, y + h);
839             ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - r);
840             ctx.lineTo(x + w, y + r);
841             ctx.quadraticCurveTo(x + w, y, x + w - r, y);
842             ctx.lineTo(x + r, y);
843             ctx.quadraticCurveTo(x, y, x, y + r);
844             ctx.closePath();
845
846             // Clip to the rounded rect path.
847             ctx.save();
848             ctx.clip();
849
850             // Fill the segments with the associated color.
851             var previousSegmentsWidth = 0;
852             for (var i = 0; i < segments.length; ++i) {
853                 var segmentWidth = Math.round(w * percents[i] / 100);
854                 ctx.fillStyle = segments[i].color;
855                 ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h);
856                 previousSegmentsWidth += segmentWidth;
857             }
858
859             // Draw the segment divider lines.
860             ctx.lineWidth = 1;
861             for (var i = 1; i < 20; ++i) {
862                 ctx.beginPath();
863                 ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y);
864                 ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h);
865                 ctx.closePath();
866
867                 ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
868                 ctx.stroke();
869
870                 ctx.beginPath();
871                 ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y);
872                 ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h);
873                 ctx.closePath();
874
875                 ctx.strokeStyle = "rgba(255, 255, 255, 0.2)";
876                 ctx.stroke();
877             }
878
879             // Draw the pill shading.
880             var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5));
881             lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)");
882             lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)");
883             lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");
884
885             var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h);
886             darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)");
887             darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)");
888             darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)");
889
890             ctx.fillStyle = darkGradient;
891             ctx.fillRect(x, y, w, h);
892
893             ctx.fillStyle = lightGradient;
894             ctx.fillRect(x, y, w, h);
895
896             ctx.restore();
897         }
898
899         ctx.clearRect(x, y, w, (h * 2));
900
901         drawPillShadow();
902         drawPill();
903
904         ctx.save();
905
906         ctx.translate(0, (h * 2) + 1);
907         ctx.scale(1, -1);
908
909         drawPill();
910
911         ctx.restore();
912
913         this._fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0);
914     },
915
916     _updateSummaryGraphSoon: function()
917     {
918         if ("_updateSummaryGraphTimeout" in this)
919             return;
920         this._updateSummaryGraphTimeout = setTimeout(this._updateSummaryGraph.bind(this), (this._showingEmptySummaryGraph ? 0 : 500));
921     },
922
923     _updateSummaryGraph: function()
924     {
925         if ("_updateSummaryGraphTimeout" in this) {
926             clearTimeout(this._updateSummaryGraphTimeout);
927             delete this._updateSummaryGraphTimeout;
928         }
929
930         var graphInfo = this.calculator.computeSummaryValues(this._resources);
931
932         var categoryOrder = ["documents", "stylesheets", "images", "scripts", "xhr", "fonts", "other"];
933         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}, xhr: {r: 231, g: 231, b: 10}, fonts: {r: 255, g: 82, b: 62}, other: {r: 186, g: 186, b: 186}};
934         var fillSegments = [];
935
936         this.legendElement.removeChildren();
937
938         if (this.totalLegendLabel)
939             this.totalLegendLabel.parentNode.removeChild(this.totalLegendLabel);
940
941         for (var i = 0; i < categoryOrder.length; ++i) {
942             var category = categoryOrder[i];
943             var size = graphInfo.categoryValues[category];
944             if (!size)
945                 continue;
946
947             var color = categoryColors[category];
948             var colorString = "rgb(" + color.r + ", " + color.g + ", " + color.b + ")";
949
950             var fillSegment = {color: colorString, value: size};
951             fillSegments.push(fillSegment);
952
953             var legendLabel = this._makeLegendElement(WebInspector.resourceCategories[category].title, this.calculator.formatValue(size), colorString);
954             this.legendElement.appendChild(legendLabel);
955         }
956
957         if (graphInfo.total) {
958             var totalLegendLabel = this._makeLegendElement(WebInspector.UIString("Total"), this.calculator.formatValue(graphInfo.total));
959             totalLegendLabel.addStyleClass("total");
960             this.legendElement.appendChild(totalLegendLabel);
961         }
962
963         this._drawSummaryGraph(fillSegments);
964     },
965
966     _updateDividersLabelBarPosition: function()
967     {
968         var scrollTop = this.containerElement.scrollTop;
969         var dividersTop = (scrollTop < this.summaryElement.offsetHeight ? this.summaryElement.offsetHeight : scrollTop);
970         this.dividersElement.style.top = scrollTop + "px";
971         this.dividersLabelBarElement.style.top = dividersTop + "px";
972     },
973
974     _graphSelected: function(treeElement)
975     {
976         if (this._lastSelectedGraphTreeElement)
977             this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = this.sortingSelectElement.selectedIndex;
978
979         this._lastSelectedGraphTreeElement = treeElement;
980
981         this.sortingSelectElement.removeChildren();
982         for (var i = 0; i < treeElement.sortingOptions.length; ++i) {
983             var sortingOption = treeElement.sortingOptions[i];
984             var option = document.createElement("option");
985             option.label = sortingOption.name;
986             option.sortingFunction = sortingOption.sortingFunction;
987             option.calculator = sortingOption.calculator;
988             this.sortingSelectElement.appendChild(option);
989         }
990
991         this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex;
992         this._changeSortingFunction();
993
994         this.closeVisibleResource();
995         this.containerElement.scrollTop = 0;
996     },
997
998     _toggleLargerResources: function()
999     {
1000         if (!this.resourcesTreeElement._childrenListNode)
1001             return;
1002
1003         if (this.resourcesTreeElement._childrenListNode.hasStyleClass("small")) {
1004             this.resourcesTreeElement._childrenListNode.removeStyleClass("small");
1005             this.largerResourcesButton.addStyleClass("toggled-on");
1006         } else {
1007             this.resourcesTreeElement._childrenListNode.addStyleClass("small");
1008             this.largerResourcesButton.removeStyleClass("toggled-on");
1009         }
1010     },
1011
1012     _changeSortingFunction: function()
1013     {
1014         var selectedOption = this.sortingSelectElement[this.sortingSelectElement.selectedIndex];
1015         this.calculator = selectedOption.calculator;
1016         this.sortingFunction = selectedOption.sortingFunction;
1017     },
1018
1019     _createResourceView: function(resource)
1020     {
1021         switch (resource.category) {
1022             case WebInspector.resourceCategories.documents:
1023             case WebInspector.resourceCategories.stylesheets:
1024             case WebInspector.resourceCategories.scripts:
1025             case WebInspector.resourceCategories.xhr:
1026                 return new WebInspector.SourceView(resource);
1027             case WebInspector.resourceCategories.images:
1028                 return new WebInspector.ImageView(resource);
1029             case WebInspector.resourceCategories.fonts:
1030                 return new WebInspector.FontView(resource);
1031             default:
1032                 return new WebInspector.ResourceView(resource);
1033         }
1034     },
1035
1036     _startSidebarDragging: function(event)
1037     {
1038         WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
1039     },
1040
1041     _sidebarDragging: function(event)
1042     {
1043         this._updateSidebarWidth(event.pageX);
1044
1045         event.preventDefault();
1046     },
1047
1048     _endSidebarDragging: function(event)
1049     {
1050         WebInspector.elementDragEnd(event);
1051     },
1052
1053     _updateSidebarWidth: function(width)
1054     {
1055         if (this.sidebarElement.offsetWidth <= 0) {
1056             // The stylesheet hasn't loaded yet or the window is closed,
1057             // so we can't calculate what is need. Return early.
1058             return;
1059         }
1060
1061         if (!("_currentSidebarWidth" in this))
1062             this._currentSidebarWidth = this.sidebarElement.offsetWidth;
1063
1064         if (typeof width === "undefined")
1065             width = this._currentSidebarWidth;
1066
1067         width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
1068
1069         this._currentSidebarWidth = width;
1070
1071         if (this.visibleResource) {
1072             this.containerElement.style.width = width + "px";
1073             this.sidebarElement.style.removeProperty("width");
1074         } else {
1075             this.sidebarElement.style.width = width + "px";
1076             this.containerElement.style.removeProperty("width");
1077         }
1078
1079         this.containerContentElement.style.left = width + "px";
1080         this.resourceViews.style.left = width + "px";
1081         this.sidebarResizeElement.style.left = (width - 3) + "px";
1082
1083         this._updateGraphBars();
1084         this._updateGraphDividersIfNeeded();
1085
1086         var visibleResourceView = this.visibleResourceView;
1087         if (visibleResourceView && "resize" in visibleResourceView)
1088             visibleResourceView.resize();
1089     }
1090 }
1091
1092 WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
1093
1094 WebInspector.ResourceCalculator = function()
1095 {
1096 }
1097
1098 WebInspector.ResourceCalculator.prototype = {
1099     computeSummaryValues: function(resources)
1100     {
1101         var total = 0;
1102         var categoryValues = {};
1103
1104         var resourcesLength = resources.length;
1105         for (var i = 0; i < resourcesLength; ++i) {
1106             var resource = resources[i];
1107             var value = this._value(resource);
1108             if (typeof value === "undefined")
1109                 continue;
1110             if (!(resource.category.name in categoryValues))
1111                 categoryValues[resource.category.name] = 0;
1112             categoryValues[resource.category.name] += value;
1113             total += value;
1114         }
1115
1116         return {categoryValues: categoryValues, total: total};
1117     },
1118
1119     computeBarGraphPercentages: function(resource)
1120     {
1121         return {start: 0, middle: 0, end: (this._value(resource) / this.boundarySpan) * 100};
1122     },
1123
1124     get boundarySpan()
1125     {
1126         return this.maximumBoundary - this.minimumBoundary;
1127     },
1128
1129     updateBoundries: function(resource)
1130     {
1131         this.minimumBoundary = 0;
1132
1133         var value = this._value(resource);
1134         if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
1135             this.maximumBoundary = value;
1136             return true;
1137         }
1138
1139         return false;
1140     },
1141
1142     reset: function()
1143     {
1144         delete this.minimumBoundary;
1145         delete this.maximumBoundary;
1146     },
1147
1148     _value: function(resource)
1149     {
1150         return 0;
1151     },
1152
1153     formatValue: function(value)
1154     {
1155         return value.toString();
1156     }
1157 }
1158
1159 WebInspector.ResourceTimeCalculator = function(startAtZero)
1160 {
1161     WebInspector.ResourceCalculator.call(this);
1162     this.startAtZero = startAtZero;
1163 }
1164
1165 WebInspector.ResourceTimeCalculator.prototype = {
1166     computeSummaryValues: function(resources)
1167     {
1168         var resourcesByCategory = {};
1169         var resourcesLength = resources.length;
1170         for (var i = 0; i < resourcesLength; ++i) {
1171             var resource = resources[i];
1172             if (!(resource.category.name in resourcesByCategory))
1173                 resourcesByCategory[resource.category.name] = [];
1174             resourcesByCategory[resource.category.name].push(resource);
1175         }
1176
1177         var earliestStart;
1178         var latestEnd;
1179         var categoryValues = {};
1180         for (var category in resourcesByCategory) {
1181             resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
1182             categoryValues[category] = 0;
1183
1184             var segment = {start: -1, end: -1};
1185
1186             var categoryResources = resourcesByCategory[category];
1187             var resourcesLength = categoryResources.length;
1188             for (var i = 0; i < resourcesLength; ++i) {
1189                 var resource = categoryResources[i];
1190                 if (resource.startTime === -1 || resource.endTime === -1)
1191                     continue;
1192
1193                 if (typeof earliestStart === "undefined")
1194                     earliestStart = resource.startTime;
1195                 else
1196                     earliestStart = Math.min(earliestStart, resource.startTime);
1197
1198                 if (typeof latestEnd === "undefined")
1199                     latestEnd = resource.endTime;
1200                 else
1201                     latestEnd = Math.max(latestEnd, resource.endTime);
1202
1203                 if (resource.startTime <= segment.end) {
1204                     segment.end = Math.max(segment.end, resource.endTime);
1205                     continue;
1206                 }
1207
1208                 categoryValues[category] += segment.end - segment.start;
1209
1210                 segment.start = resource.startTime;
1211                 segment.end = resource.endTime;
1212             }
1213
1214             // Add the last segment
1215             categoryValues[category] += segment.end - segment.start;
1216         }
1217
1218         return {categoryValues: categoryValues, total: latestEnd - earliestStart};
1219     },
1220
1221     computeBarGraphPercentages: function(resource)
1222     {
1223         if (resource.startTime !== -1)
1224             var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
1225         else
1226             var start = 100;
1227         
1228         if (resource.responseReceivedTime !== -1)
1229             var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
1230         else
1231             var middle = 100;
1232         
1233         if (resource.endTime !== -1)
1234             var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
1235         else
1236             var end = 100;
1237
1238         if (this.startAtZero) {
1239             end -= start;
1240             middle -= start;
1241             start = 0;
1242         }
1243
1244         return {start: start, middle: middle, end: end};
1245     },
1246
1247     updateBoundries: function(resource)
1248     {
1249         var didChange = false;
1250
1251         var lowerBound;
1252         if (this.startAtZero)
1253             lowerBound = 0;
1254         else
1255             lowerBound = this._lowerBound(resource);
1256
1257         if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
1258             this.minimumBoundary = lowerBound;
1259             didChange = true;
1260         }
1261
1262         var upperBound = this._upperBound(resource);
1263         if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
1264             this.maximumBoundary = upperBound;
1265             didChange = true;
1266         }
1267
1268         return didChange;
1269     },
1270
1271     formatValue: function(value)
1272     {
1273         return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
1274     },
1275
1276     _lowerBound: function(resource)
1277     {
1278         return 0;
1279     },
1280
1281     _upperBound: function(resource)
1282     {
1283         return 0;
1284     },
1285 }
1286
1287 WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype;
1288
1289 WebInspector.ResourceTransferTimeCalculator = function()
1290 {
1291     WebInspector.ResourceTimeCalculator.call(this, false);
1292 }
1293
1294 WebInspector.ResourceTransferTimeCalculator.prototype = {
1295     formatValue: function(value)
1296     {
1297         return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
1298     },
1299
1300     _lowerBound: function(resource)
1301     {
1302         return resource.startTime;
1303     },
1304
1305     _upperBound: function(resource)
1306     {
1307         return resource.endTime;
1308     },
1309 }
1310
1311 WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
1312
1313 WebInspector.ResourceTransferDurationCalculator = function()
1314 {
1315     WebInspector.ResourceTimeCalculator.call(this, true);
1316 }
1317
1318 WebInspector.ResourceTransferDurationCalculator.prototype = {
1319     formatValue: function(value)
1320     {
1321         return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
1322     },
1323
1324     _upperBound: function(resource)
1325     {
1326         return resource.duration;
1327     },
1328 }
1329
1330 WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
1331
1332 WebInspector.ResourceTransferSizeCalculator = function()
1333 {
1334     WebInspector.ResourceCalculator.call(this);
1335 }
1336
1337 WebInspector.ResourceTransferSizeCalculator.prototype = {
1338     _value: function(resource)
1339     {
1340         return resource.contentLength;
1341     },
1342
1343     formatValue: function(value)
1344     {
1345         return Number.bytesToString(value, WebInspector.UIString.bind(WebInspector));
1346     }
1347 }
1348
1349 WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype;
1350
1351 WebInspector.ResourceSidebarTreeElement = function(resource)
1352 {
1353     this.resource = resource;
1354
1355     WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource);
1356
1357     this.refreshTitles();
1358
1359     this.graphSideElement = document.createElement("div");
1360     this.graphSideElement.className = "resources-graph-side";
1361
1362     this.barAreaElement = document.createElement("div");
1363     this.barAreaElement.className = "resources-graph-bar-area";
1364     this.graphSideElement.appendChild(this.barAreaElement);
1365
1366     this.barLeftElement = document.createElement("div");
1367     this.barLeftElement.className = "resources-graph-bar waiting";
1368     this.barAreaElement.appendChild(this.barLeftElement);
1369     
1370     this.barRightElement = document.createElement("div");
1371     this.barRightElement.className = "resources-graph-bar";
1372     this.barAreaElement.appendChild(this.barRightElement);
1373 }
1374
1375 WebInspector.ResourceSidebarTreeElement.prototype = {
1376     onattach: function()
1377     {
1378         WebInspector.SidebarTreeElement.prototype.onattach.call(this);
1379
1380         this._listItemNode.appendChild(this.graphSideElement);
1381         this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
1382     },
1383
1384     onselect: function()
1385     {
1386         WebInspector.panels.resources.showResource(this.resource);
1387     },
1388
1389     get mainTitle()
1390     {
1391         return this.resource.displayName;
1392     },
1393
1394     set mainTitle(x)
1395     {
1396         // Do nothing.
1397     },
1398
1399     get subtitle()
1400     {
1401         var subtitle = this.resource.displayDomain;
1402
1403         if (this.resource.path && this.resource.lastPathComponent) {
1404             var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent);
1405             if (lastPathComponentIndex != -1)
1406                 subtitle += this.resource.path.substring(0, lastPathComponentIndex);
1407         }
1408
1409         return subtitle;
1410     },
1411
1412     set subtitle(x)
1413     {
1414         // Do nothing.
1415     },
1416
1417     refresh: function()
1418     {
1419         this.refreshTitles();
1420
1421         var newClassName = "sidebar-tree-item resource-sidebar-tree-item resources-category-" + this.resource.category.name;
1422         if (this._listItemNode && this._listItemNode.className !== newClassName)
1423             this._listItemNode.className = newClassName;
1424     },
1425
1426     updateErrorsAndWarnings: function()
1427     {
1428         if (this.resource.warnings || this.resource.errors)
1429             this.bubbleText = (this.resource.warnings + this.resource.errors);
1430         else
1431             this.bubbleText = "";
1432
1433         if (this.resource.warnings)
1434             this.bubbleElement.addStyleClass("warning");
1435         else
1436             this.bubbleElement.removeStyleClass("warning");
1437
1438         if (this.resource.errors)
1439             this.bubbleElement.addStyleClass("error");
1440         else
1441             this.bubbleElement.removeStyleClass("error");
1442     },
1443
1444     updateGraphSideWidth: function(width)
1445     {
1446         width += 1; // Add one to account for the sidebar border width.
1447         this.graphSideElement.style.right = -width + "px";
1448         this.graphSideElement.style.width = width + "px";
1449     }
1450 }
1451
1452 WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b)
1453 {
1454     return WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1455         || WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
1456         || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
1457 }
1458
1459 WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b)
1460 {
1461     return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource)
1462         || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1463         || WebInspector.Resource.CompareByEndTime(a.resource, b.resource);
1464 }
1465
1466 WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b)
1467 {
1468     return WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
1469         || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1470         || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
1471 }
1472
1473 WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b)
1474 {
1475     return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource);
1476 }
1477
1478 WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b)
1479 {
1480     return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource);
1481 }
1482
1483 WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b)
1484 {
1485     return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource);
1486 }
1487
1488 WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;