4fda07bc674d482de0ecf1804c7f0a83ce68659e
[WebKit-https.git] / WebCore / inspector / front-end / ResourcesPanel.js
1 /*
2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
3  * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer. 
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution. 
14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission. 
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 WebInspector.ResourcesPanel = function()
31 {
32     WebInspector.AbstractTimelinePanel.call(this, "resources");
33
34     this._createPanelEnabler();
35
36     this.viewsContainerElement = document.createElement("div");
37     this.viewsContainerElement.id = "resource-views";
38     this.element.appendChild(this.viewsContainerElement);
39
40     this.createFilterPanel();
41     this.createInterface();
42
43     this._createStatusbarButtons();
44     this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true);
45
46     this.reset();
47     this.filter(this.filterAllElement, false);
48     this.graphsTreeElement.children[0].select();
49     this._resourceTrackingEnabled = false;
50 }
51
52 WebInspector.ResourcesPanel.prototype = {
53     get toolbarItemLabel()
54     {
55         return WebInspector.UIString("Resources");
56     },
57
58     get statusBarItems()
59     {
60         return [this.enableToggleButton.element, this.largerResourcesButton.element, this.sortingSelectElement];
61     },
62
63     get categories()
64     {
65         return WebInspector.resourceCategories;
66     },
67
68     createItemTreeElement: function(item)
69     {
70         return new WebInspector.ResourceSidebarTreeElement(item);
71     },
72
73     createItemGraph: function(item)
74     {
75         return new WebInspector.ResourceGraph(item);
76     },
77
78     isCategoryVisible: function(categoryName)
79     {
80         return (this.itemsGraphsElement.hasStyleClass("filter-all") || this.itemsGraphsElement.hasStyleClass("filter-" + categoryName.toLowerCase()));
81     },
82
83     populateSidebar: function()
84     {
85         this.timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time"));
86         this.timeGraphItem.onselect = this._graphSelected.bind(this);
87
88         var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator();
89         var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator();
90
91         this.timeGraphItem.sortingOptions = [
92             { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator, optionName: "startTime" },
93             { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator, optionName: "responseTime" },
94             { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator, optionName: "endTime" },
95             { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator, optionName: "duration" },
96             { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator, optionName: "latency" },
97         ];
98
99         this.timeGraphItem.isBarOpaqueAtLeft = false;
100         this.timeGraphItem.selectedSortingOptionIndex = 1;
101
102         this.sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size"));
103         this.sizeGraphItem.onselect = this._graphSelected.bind(this);
104
105         var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator();
106         this.sizeGraphItem.sortingOptions = [
107             { name: WebInspector.UIString("Sort by Transfer Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingTransferSize, calculator: transferSizeCalculator, optionName: "transferSize" },
108             { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator, optionName: "size" },
109         ];
110
111         this.sizeGraphItem.isBarOpaqueAtLeft = true;
112         this.sizeGraphItem.selectedSortingOptionIndex = 0;
113
114         this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true);
115         this.sidebarTree.appendChild(this.graphsTreeElement);
116
117         this.graphsTreeElement.appendChild(this.timeGraphItem);
118         this.graphsTreeElement.appendChild(this.sizeGraphItem);
119         this.graphsTreeElement.expand();
120
121         this.itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true);
122         this.sidebarTree.appendChild(this.itemsTreeElement);
123
124         this.itemsTreeElement.expand();
125     },
126
127     get resourceTrackingEnabled()
128     {
129         return this._resourceTrackingEnabled;
130     },
131
132     _createPanelEnabler: function()
133     {
134         var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel.");
135         var panelEnablerDisclaimer = WebInspector.UIString("Enabling resource tracking will reload the page and make page loading slower.");
136         var panelEnablerButton = WebInspector.UIString("Enable resource tracking");
137
138         this.panelEnablerView = new WebInspector.PanelEnablerView("resources", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
139         this.panelEnablerView.addEventListener("enable clicked", this._enableResourceTracking, this);
140
141         this.element.appendChild(this.panelEnablerView.element);
142
143         this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
144         this.enableToggleButton.addEventListener("click", this._toggleResourceTracking.bind(this), false);
145     },
146
147     _createStatusbarButtons: function()
148     {
149         this.largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "resources-larger-resources-status-bar-item");
150
151         WebInspector.applicationSettings.addEventListener("loaded", this._settingsLoaded, this);
152         this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
153         this.sortingSelectElement = document.createElement("select");
154         this.sortingSelectElement.className = "status-bar-item";
155         this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false);
156     },
157
158     _settingsLoaded: function()
159     {
160         this.largerResourcesButton.toggled = WebInspector.applicationSettings.resourcesLargeRows;
161         if (!WebInspector.applicationSettings.resourcesLargeRows)
162             this._setLargerResources(WebInspector.applicationSettings.resourcesLargeRows);
163         this._loadSortOptions();
164     },
165
166     _loadSortOptions: function()
167     {
168         var newOptions = WebInspector.applicationSettings.resourcesSortOptions;
169         if (!newOptions)
170             return;
171
172         this._loadSortOptionForGraph(this.timeGraphItem, newOptions.timeOption || "responseTime");
173         this._loadSortOptionForGraph(this.sizeGraphItem, newOptions.sizeOption || "transferSize");
174     },
175
176     _loadSortOptionForGraph: function(graphItem, newOptionName)
177     {
178         var sortingOptions = graphItem.sortingOptions;
179         for (var i = 0; i < sortingOptions.length; ++i) {
180             if (sortingOptions[i].optionName === newOptionName) {
181                 graphItem.selectedSortingOptionIndex = i;
182                 // Propagate the option change down to the currently selected option.
183                 if (this._lastSelectedGraphTreeElement === graphItem) {
184                     this._lastSelectedGraphTreeElement = null;
185                     this._graphSelected(graphItem);
186                 }
187             }
188         }
189     },
190
191     get mainResourceLoadTime()
192     {
193         return this._mainResourceLoadTime || -1;
194     },
195     
196     set mainResourceLoadTime(x)
197     {
198         if (this._mainResourceLoadTime === x)
199             return;
200         
201         this._mainResourceLoadTime = x;
202         
203         // Update the dividers to draw the new line
204         this.updateGraphDividersIfNeeded(true);
205     },
206     
207     get mainResourceDOMContentTime()
208     {
209         return this._mainResourceDOMContentTime || -1;
210     },
211     
212     set mainResourceDOMContentTime(x)
213     {
214         if (this._mainResourceDOMContentTime === x)
215             return;
216         
217         this._mainResourceDOMContentTime = x;
218         
219         this.updateGraphDividersIfNeeded(true);
220     },
221
222     show: function()
223     {
224         WebInspector.AbstractTimelinePanel.prototype.show.call(this);
225
226         var visibleView = this.visibleView;
227         if (this.visibleResource) {
228             this.visibleView.headersVisible = true;
229             this.visibleView.show(this.viewsContainerElement);
230         } else if (visibleView)
231             visibleView.show();
232
233         // Hide any views that are visible that are not this panel's current visible view.
234         // This can happen when a ResourceView is visible in the Scripts panel then switched
235         // to the this panel.
236         var resourcesLength = this._resources.length;
237         for (var i = 0; i < resourcesLength; ++i) {
238             var resource = this._resources[i];
239             var view = resource._resourcesView;
240             if (!view || view === visibleView)
241                 continue;
242             view.visible = false;
243         }
244     },
245
246     get searchableViews()
247     {
248         var views = [];
249
250         const visibleView = this.visibleView;
251         if (visibleView && visibleView.performSearch)
252             views.push(visibleView);
253
254         var resourcesLength = this._resources.length;
255         for (var i = 0; i < resourcesLength; ++i) {
256             var resource = this._resources[i];
257             if (!resource._itemsTreeElement || !resource._itemsTreeElement.selectable)
258                 continue;
259             var resourceView = this.resourceViewForResource(resource);
260             if (!resourceView.performSearch || resourceView === visibleView)
261                 continue;
262             views.push(resourceView);
263         }
264
265         return views;
266     },
267
268     get searchResultsSortFunction()
269     {
270         const resourceTreeElementSortFunction = this.sortingFunction;
271
272         function sortFuction(a, b)
273         {
274             return resourceTreeElementSortFunction(a.resource._itemsTreeElement, b.resource._itemsTreeElement);
275         }
276
277         return sortFuction;
278     },
279
280     searchMatchFound: function(view, matches)
281     {
282         view.resource._itemsTreeElement.searchMatches = matches;
283     },
284
285     searchCanceled: function(startingNewSearch)
286     {
287         WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
288
289         if (startingNewSearch || !this._resources)
290             return;
291
292         for (var i = 0; i < this._resources.length; ++i) {
293             var resource = this._resources[i];
294             if (resource._itemsTreeElement)
295                 resource._itemsTreeElement.updateErrorsAndWarnings();
296         }
297     },
298
299     performSearch: function(query)
300     {
301         for (var i = 0; i < this._resources.length; ++i) {
302             var resource = this._resources[i];
303             if (resource._itemsTreeElement)
304                 resource._itemsTreeElement.resetBubble();
305         }
306
307         WebInspector.Panel.prototype.performSearch.call(this, query);
308     },
309
310     get visibleView()
311     {
312         if (this.visibleResource)
313             return this.visibleResource._resourcesView;
314         return this._resourceTrackingEnabled ? null : this.panelEnablerView;
315     },
316
317     get sortingFunction()
318     {
319         return this._sortingFunction;
320     },
321
322     set sortingFunction(x)
323     {
324         this._sortingFunction = x;
325         this._sortResourcesIfNeeded();
326     },
327
328     refresh: function()
329     {
330         WebInspector.AbstractTimelinePanel.prototype.refresh.call(this);
331
332         this._sortResourcesIfNeeded();
333         this._updateSummaryGraph();
334     },
335
336     _updateSummaryGraph: function()
337     {
338         this.summaryBar.update(this._resources);
339     },
340
341     resourceTrackingWasEnabled: function()
342     {
343         this._resourceTrackingEnabled = true;
344         this.reset();
345         this.restoreSidebarWidth();
346     },
347
348     resourceTrackingWasDisabled: function()
349     {
350         this._resourceTrackingEnabled = false;
351         this.reset();
352     },
353
354     reset: function()
355     {
356         this._popoverHelper.hidePopup();
357         this.closeVisibleResource();
358
359         delete this.currentQuery;
360         this.searchCanceled();
361
362         if (this._resources) {
363             var resourcesLength = this._resources.length;
364             for (var i = 0; i < resourcesLength; ++i) {
365                 var resource = this._resources[i];
366
367                 resource.warnings = 0;
368                 resource.errors = 0;
369
370                 delete resource._resourcesView;
371             }
372         }
373
374         WebInspector.AbstractTimelinePanel.prototype.reset.call(this);
375         
376         this.mainResourceLoadTime = -1;
377         this.mainResourceDOMContentTime = -1;
378  
379         this.viewsContainerElement.removeChildren();
380
381         this.summaryBar.reset();
382
383         if (this._resourceTrackingEnabled) {
384             this.enableToggleButton.title = WebInspector.UIString("Resource tracking enabled. Click to disable.");
385             this.enableToggleButton.toggled = true;
386             this.largerResourcesButton.visible = true;
387             this.sortingSelectElement.removeStyleClass("hidden");
388             this.panelEnablerView.visible = false;
389         } else {
390             this.enableToggleButton.title = WebInspector.UIString("Resource tracking disabled. Click to enable.");
391             this.enableToggleButton.toggled = false;
392             this.largerResourcesButton.visible = false;
393             this.sortingSelectElement.addStyleClass("hidden");
394             this.panelEnablerView.visible = true;
395         }
396     },
397
398     addResource: function(resource)
399     {
400         this._resources.push(resource);
401         this.refreshResource(resource);
402     },
403
404     removeResource: function(resource)
405     {
406         if (this.visibleView === resource._resourcesView)
407             this.closeVisibleResource();
408
409         this.removeItem(resource);
410
411         resource.warnings = 0;
412         resource.errors = 0;
413
414         delete resource._resourcesView;
415     },
416
417     addMessageToResource: function(resource, msg)
418     {
419         if (!resource)
420             return;
421
422         switch (msg.level) {
423         case WebInspector.ConsoleMessage.MessageLevel.Warning:
424             resource.warnings += msg.repeatDelta;
425             break;
426         case WebInspector.ConsoleMessage.MessageLevel.Error:
427             resource.errors += msg.repeatDelta;
428             break;
429         }
430
431         if (!this.currentQuery && resource._itemsTreeElement)
432             resource._itemsTreeElement.updateErrorsAndWarnings();
433
434         var view = this.resourceViewForResource(resource);
435         if (view.addMessage)
436             view.addMessage(msg);
437     },
438
439     clearMessages: function()
440     {
441         var resourcesLength = this._resources.length;
442         for (var i = 0; i < resourcesLength; ++i) {
443             var resource = this._resources[i];
444             resource.warnings = 0;
445             resource.errors = 0;
446
447             if (!this.currentQuery && resource._itemsTreeElement)
448                 resource._itemsTreeElement.updateErrorsAndWarnings();
449
450             var view = resource._resourcesView;
451             if (!view || !view.clearMessages)
452                 continue;
453             view.clearMessages();
454         }
455     },
456
457     refreshResource: function(resource)
458     {
459         this.refreshItem(resource);
460     },
461
462     recreateViewForResourceIfNeeded: function(resource)
463     {
464         if (!resource || !resource._resourcesView)
465             return;
466
467         var newView = this._createResourceView(resource);
468         if (newView.__proto__ === resource._resourcesView.__proto__)
469             return;
470
471         if (!this.currentQuery && resource._itemsTreeElement)
472             resource._itemsTreeElement.updateErrorsAndWarnings();
473
474         var oldView = resource._resourcesView;
475         var oldViewParentNode = oldView.visible ? oldView.element.parentNode : null;
476
477         resource._resourcesView.detach();
478         delete resource._resourcesView;
479
480         resource._resourcesView = newView;
481
482         newView.headersVisible = oldView.headersVisible;
483
484         if (oldViewParentNode)
485             newView.show(oldViewParentNode);
486
487         WebInspector.panels.scripts.viewRecreated(oldView, newView);
488     },
489
490     canShowSourceLine: function(url, line)
491     {
492         return this._resourceTrackingEnabled && !!WebInspector.resourceForURL(url);
493     },
494
495     showSourceLine: function(url, line)
496     {
497         this.showResource(WebInspector.resourceForURL(url), line);
498     },
499
500     showResource: function(resource, line)
501     {
502         if (!resource)
503             return;
504
505         this._popoverHelper.hidePopup();
506
507         this.containerElement.addStyleClass("viewing-resource");
508
509         if (this.visibleResource && this.visibleResource._resourcesView)
510             this.visibleResource._resourcesView.hide();
511
512         var view = this.resourceViewForResource(resource);
513         view.headersVisible = true;
514         view.show(this.viewsContainerElement);
515
516         if (line) {
517             view.selectContentTab(true);
518             if (view.revealLine)
519                 view.revealLine(line);
520             if (view.highlightLine)
521                 view.highlightLine(line);
522         }
523
524         this.revealAndSelectItem(resource);
525
526         this.visibleResource = resource;
527
528         this.updateSidebarWidth();
529     },
530
531     showView: function(view)
532     {
533         if (!view)
534             return;
535         this.showResource(view.resource);
536     },
537
538     closeVisibleResource: function()
539     {
540         this.containerElement.removeStyleClass("viewing-resource");
541         this._updateDividersLabelBarPosition();
542
543         if (this.visibleResource && this.visibleResource._resourcesView)
544             this.visibleResource._resourcesView.hide();
545         delete this.visibleResource;
546
547         if (this._lastSelectedGraphTreeElement)
548             this._lastSelectedGraphTreeElement.select(true);
549
550         this.updateSidebarWidth();
551     },
552
553     resourceViewForResource: function(resource)
554     {
555         if (!resource)
556             return null;
557         if (!resource._resourcesView)
558             resource._resourcesView = this._createResourceView(resource);
559         return resource._resourcesView;
560     },
561
562     sourceFrameForResource: function(resource)
563     {
564         var view = this.resourceViewForResource(resource);
565         if (!view)
566             return null;
567
568         if (!view.setupSourceFrameIfNeeded)
569             return null;
570
571         // Setting up the source frame requires that we be attached.
572         if (!this.element.parentNode)
573             this.attach();
574
575         view.setupSourceFrameIfNeeded();
576         return view.sourceFrame;
577     },
578
579     _sortResourcesIfNeeded: function()
580     {
581         this.sortItems(this.sortingFunction);
582     },
583
584     updateGraphDividersIfNeeded: function(force)
585     {
586         var proceed = WebInspector.AbstractTimelinePanel.prototype.updateGraphDividersIfNeeded.call(this, force);
587         
588         if (!proceed)
589             return;
590
591         if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) {
592             // If our current sorting method starts at zero, that means it shows all
593             // resources starting at the same point, and so onLoad event and DOMContent
594             // event lines really wouldn't make much sense here, so don't render them.
595             // Additionally, if the calculator doesn't have the computePercentageFromEventTime
596             // function defined, we are probably sorting by size, and event times aren't relevant
597             // in this case.
598             return;
599         }
600
601         this._timelineGrid.removeEventDividers();
602         if (this.mainResourceLoadTime !== -1) {
603             var percent = this.calculator.computePercentageFromEventTime(this.mainResourceLoadTime);
604
605             var loadDivider = document.createElement("div");
606             loadDivider.className = "resources-event-divider resources-red-divider";
607
608             var loadDividerPadding = document.createElement("div");
609             loadDividerPadding.className = "resources-event-divider-padding";
610             loadDividerPadding.style.left = percent + "%";
611             loadDividerPadding.title = WebInspector.UIString("Load event fired");
612             loadDividerPadding.appendChild(loadDivider);
613
614             this.addEventDivider(loadDividerPadding);
615         }
616         
617         if (this.mainResourceDOMContentTime !== -1) {
618             var percent = this.calculator.computePercentageFromEventTime(this.mainResourceDOMContentTime);
619
620             var domContentDivider = document.createElement("div");
621             domContentDivider.className = "resources-event-divider resources-blue-divider";
622             
623             var domContentDividerPadding = document.createElement("div");
624             domContentDividerPadding.className = "resources-event-divider-padding";
625             domContentDividerPadding.style.left = percent + "%";
626             domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired");
627             domContentDividerPadding.appendChild(domContentDivider);
628
629             this.addEventDivider(domContentDividerPadding);
630         }
631     },
632
633     _graphSelected: function(treeElement)
634     {
635         if (this._lastSelectedGraphTreeElement)
636             this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = this.sortingSelectElement.selectedIndex;
637
638         this._lastSelectedGraphTreeElement = treeElement;
639
640         this.sortingSelectElement.removeChildren();
641         for (var i = 0; i < treeElement.sortingOptions.length; ++i) {
642             var sortingOption = treeElement.sortingOptions[i];
643             var option = document.createElement("option");
644             option.label = sortingOption.name;
645             option.sortingFunction = sortingOption.sortingFunction;
646             option.calculator = sortingOption.calculator;
647             option.optionName = sortingOption.optionName;
648             this.sortingSelectElement.appendChild(option);
649         }
650
651         this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex;
652         this._doChangeSortingFunction();
653
654         this.closeVisibleResource();
655         this.containerElement.scrollTop = 0;
656
657         if (treeElement === this.sizeGraphItem)
658             this.hideEventDividers();
659         else
660             this.showEventDividers();
661     },
662
663     _toggleLargerResources: function()
664     {
665         if (!this.itemsTreeElement._childrenListNode)
666             return;
667
668         WebInspector.applicationSettings.resourcesLargeRows = !WebInspector.applicationSettings.resourcesLargeRows;
669         this._setLargerResources(this.itemsTreeElement.smallChildren);
670     },
671
672     _setLargerResources: function(enabled)
673     {
674         this.largerResourcesButton.toggled = enabled;
675         this.itemsTreeElement.smallChildren = !enabled;
676         if (!enabled) {
677             this.itemsGraphsElement.addStyleClass("small");
678             this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
679             this.adjustScrollPosition();
680         } else {
681             this.itemsGraphsElement.removeStyleClass("small");
682             this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
683         }
684     },
685
686     _changeSortingFunction: function()
687     {
688         this._doChangeSortingFunction();
689         WebInspector.applicationSettings.resourcesSortOptions = {timeOption: this._selectedOptionNameForGraph(this.timeGraphItem), sizeOption: this._selectedOptionNameForGraph(this.sizeGraphItem)};
690     },
691
692     _selectedOptionNameForGraph: function(graphItem)
693     {
694         return graphItem.sortingOptions[graphItem.selectedSortingOptionIndex].optionName;
695     },
696
697     _doChangeSortingFunction: function()
698     {
699         var selectedIndex = this.sortingSelectElement.selectedIndex;
700         if (this._lastSelectedGraphTreeElement)
701             this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = selectedIndex;
702         var selectedOption = this.sortingSelectElement[selectedIndex];
703         this.sortingFunction = selectedOption.sortingFunction;
704         this.calculator = this.summaryBar.calculator = selectedOption.calculator;
705     },
706
707     _createResourceView: function(resource)
708     {
709         switch (resource.category) {
710             case WebInspector.resourceCategories.documents:
711             case WebInspector.resourceCategories.stylesheets:
712             case WebInspector.resourceCategories.scripts:
713             case WebInspector.resourceCategories.xhr:
714                 return new WebInspector.SourceView(resource);
715             case WebInspector.resourceCategories.images:
716                 return new WebInspector.ImageView(resource);
717             case WebInspector.resourceCategories.fonts:
718                 return new WebInspector.FontView(resource);
719             default:
720                 return new WebInspector.ResourceView(resource);
721         }
722     },
723
724     setSidebarWidth: function(width)
725     {
726         if (this.visibleResource) {
727             this.containerElement.style.width = width + "px";
728             this.sidebarElement.style.removeProperty("width");
729         } else {
730             this.sidebarElement.style.width = width + "px";
731             this.containerElement.style.removeProperty("width");
732         }
733
734         this.sidebarResizeElement.style.left = (width - 3) + "px";
735     },
736
737     updateMainViewWidth: function(width)
738     {
739         this.viewsContainerElement.style.left = width + "px";
740
741         WebInspector.AbstractTimelinePanel.prototype.updateMainViewWidth.call(this, width);
742         this.resize();
743     },
744
745     _enableResourceTracking: function()
746     {
747         if (this._resourceTrackingEnabled)
748             return;
749         this._toggleResourceTracking(this.panelEnablerView.alwaysEnabled);
750     },
751
752     _toggleResourceTracking: function(optionalAlways)
753     {
754         if (this._resourceTrackingEnabled) {
755             this.largerResourcesButton.visible = false;
756             this.sortingSelectElement.visible = false;
757             WebInspector.resources = {};
758             WebInspector.resourceURLMap = {};
759             InspectorBackend.disableResourceTracking(true);
760         } else {
761             this.largerResourcesButton.visible = true;
762             this.sortingSelectElement.visible = true;
763             InspectorBackend.enableResourceTracking(!!optionalAlways);
764         }
765     },
766
767     get _resources()
768     {
769         return this.items;
770     },
771
772     searchIteratesOverViews: function()
773     {
774         return true;
775     },
776
777     elementsToRestoreScrollPositionsFor: function()
778     {
779         return [ this.containerElement ];
780     },
781
782     _getPopoverAnchor: function(element)
783     {
784         var anchor = element.enclosingNodeOrSelfWithClass("resources-graph-bar") || element.enclosingNodeOrSelfWithClass("resources-graph-label");
785         if (!anchor)
786             return null;
787         var resource = anchor.parentElement.resource;
788         return resource && resource.timing ? anchor : null;
789     },
790
791     _showPopover: function(anchor)
792     {
793         var tableElement = document.createElement("table");
794         var resource = anchor.parentElement.resource;
795         var rows = [];
796
797         function addRow(title, start, end, color)
798         {
799             var row = {};
800             row.title = title;
801             row.start = start;
802             row.end = end;
803             rows.push(row);
804         }
805
806         if (resource.timing.proxyStart !== -1)
807             addRow(WebInspector.UIString("Proxy"), resource.timing.proxyStart, resource.timing.proxyEnd);
808
809         if (resource.timing.dnsStart !== -1) {
810             addRow(WebInspector.UIString("DNS Lookup"), resource.timing.dnsStart, resource.timing.dnsEnd);
811         }
812
813         if (resource.timing.connectStart !== -1) {
814             if (resource.connectionReused)
815                 addRow(WebInspector.UIString("Blocking"), resource.timing.connectStart, resource.timing.connectEnd);
816             else {
817                 var connectStart = resource.timing.connectStart;
818                 // Connection includes DNS, subtract it here.
819                 if (resource.timing.dnsStart !== -1)
820                     connectStart += resource.timing.dnsEnd - resource.timing.dnsStart;
821                 addRow(WebInspector.UIString("Connecting"), connectStart, resource.timing.connectEnd);
822             }
823         }
824
825         if (resource.timing.sslStart !== -1)
826             addRow(WebInspector.UIString("SSL"), resource.timing.sslStart, resource.timing.sslEnd);
827
828         var sendStart = resource.timing.sendStart;
829         if (resource.timing.sslStart !== -1)
830             sendStart += resource.timing.sslEnd - resource.timing.sslStart;
831         
832         addRow(WebInspector.UIString("Sending"), resource.timing.sendStart, resource.timing.sendEnd);
833         addRow(WebInspector.UIString("Waiting"), resource.timing.sendEnd, resource.timing.receiveHeadersEnd);
834         addRow(WebInspector.UIString("Receiving"), (resource.responseReceivedTime - resource.timing.requestTime) * 1000, (resource.endTime - resource.timing.requestTime) * 1000);
835
836         const chartWidth = 200;
837         var total = (resource.endTime - resource.timing.requestTime) * 1000;
838         var scale = chartWidth / total;
839
840         for (var i = 0; i < rows.length; ++i) {
841             var tr = document.createElement("tr");
842             tableElement.appendChild(tr);
843
844             var td = document.createElement("td");
845             td.textContent = rows[i].title;
846             tr.appendChild(td);
847
848             td = document.createElement("td");
849             td.width = chartWidth + "px";
850
851             var row = document.createElement("div");
852             row.className = "resource-timing-row";
853             td.appendChild(row);
854
855             var bar = document.createElement("span");
856             bar.className = "resource-timing-bar";
857             bar.style.left = scale * rows[i].start + "px";
858             bar.style.right = scale * (total - rows[i].end) + "px";
859             bar.style.backgroundColor = rows[i].color;
860             bar.textContent = "\u200B"; // Important for 0-time items to have 0 width.
861             row.appendChild(bar);
862
863             var title = document.createElement("span");
864             title.className = "resource-timing-bar-title";
865             if (i >= rows.length - 2)
866                 title.style.right = (scale * (total - rows[i].end) + 3) + "px";
867             else
868                 title.style.left = (scale * rows[i].start + 3) + "px";
869             title.textContent = Number.millisToString(rows[i].end - rows[i].start);
870             row.appendChild(title);
871
872             tr.appendChild(td);
873         }
874
875         var popover = new WebInspector.Popover(tableElement);
876         popover.show(anchor);
877         return popover;
878     },
879
880     hide: function()
881     {
882         WebInspector.Panel.prototype.hide.call(this);
883         this._popoverHelper.hidePopup();
884     }
885 }
886
887 WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.AbstractTimelinePanel.prototype;
888
889 WebInspector.getResourceContent = function(identifier, callback)
890 {
891     InspectorBackend.getResourceContent(WebInspector.Callback.wrap(callback), identifier);
892 }
893
894 WebInspector.didGetResourceContent = WebInspector.Callback.processCallback;
895
896 WebInspector.ResourceTimeCalculator = function(startAtZero)
897 {
898     WebInspector.AbstractTimelineCalculator.call(this);
899     this.startAtZero = startAtZero;
900 }
901
902 WebInspector.ResourceTimeCalculator.prototype = {
903     computeSummaryValues: function(resources)
904     {
905         var resourcesByCategory = {};
906         var resourcesLength = resources.length;
907         for (var i = 0; i < resourcesLength; ++i) {
908             var resource = resources[i];
909             if (!(resource.category.name in resourcesByCategory))
910                 resourcesByCategory[resource.category.name] = [];
911             resourcesByCategory[resource.category.name].push(resource);
912         }
913
914         var earliestStart;
915         var latestEnd;
916         var categoryValues = {};
917         for (var category in resourcesByCategory) {
918             resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
919             categoryValues[category] = 0;
920
921             var segment = {start: -1, end: -1};
922
923             var categoryResources = resourcesByCategory[category];
924             var resourcesLength = categoryResources.length;
925             for (var i = 0; i < resourcesLength; ++i) {
926                 var resource = categoryResources[i];
927                 if (resource.startTime === -1 || resource.endTime === -1)
928                     continue;
929
930                 if (typeof earliestStart === "undefined")
931                     earliestStart = resource.startTime;
932                 else
933                     earliestStart = Math.min(earliestStart, resource.startTime);
934
935                 if (typeof latestEnd === "undefined")
936                     latestEnd = resource.endTime;
937                 else
938                     latestEnd = Math.max(latestEnd, resource.endTime);
939
940                 if (resource.startTime <= segment.end) {
941                     segment.end = Math.max(segment.end, resource.endTime);
942                     continue;
943                 }
944
945                 categoryValues[category] += segment.end - segment.start;
946
947                 segment.start = resource.startTime;
948                 segment.end = resource.endTime;
949             }
950
951             // Add the last segment
952             categoryValues[category] += segment.end - segment.start;
953         }
954
955         return {categoryValues: categoryValues, total: latestEnd - earliestStart};
956     },
957
958     computeBarGraphPercentages: function(resource)
959     {
960         if (resource.startTime !== -1)
961             var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
962         else
963             var start = 0;
964
965         if (resource.responseReceivedTime !== -1)
966             var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
967         else
968             var middle = (this.startAtZero ? start : 100);
969
970         if (resource.endTime !== -1)
971             var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
972         else
973             var end = (this.startAtZero ? middle : 100);
974
975         if (this.startAtZero) {
976             end -= start;
977             middle -= start;
978             start = 0;
979         }
980
981         return {start: start, middle: middle, end: end};
982     },
983     
984     computePercentageFromEventTime: function(eventTime)
985     {
986         // This function computes a percentage in terms of the total loading time
987         // of a specific event. If startAtZero is set, then this is useless, and we
988         // want to return 0.
989         if (eventTime !== -1 && !this.startAtZero)
990             return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;
991
992         return 0;
993     },
994
995     computeBarGraphLabels: function(resource)
996     {
997         var rightLabel = "";
998         if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
999             rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
1000
1001         var hasLatency = resource.latency > 0;
1002         if (hasLatency)
1003             var leftLabel = this.formatValue(resource.latency);
1004         else
1005             var leftLabel = rightLabel;
1006
1007         if (resource.timing)
1008             return {left: leftLabel, right: rightLabel};
1009
1010         if (hasLatency && rightLabel) {
1011             var total = this.formatValue(resource.duration);
1012             var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
1013         } else if (hasLatency)
1014             var tooltip = WebInspector.UIString("%s latency", leftLabel);
1015         else if (rightLabel)
1016             var tooltip = WebInspector.UIString("%s download", rightLabel);
1017
1018         if (resource.cached)
1019             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1020         return {left: leftLabel, right: rightLabel, tooltip: tooltip};
1021     },
1022
1023     updateBoundaries: function(resource)
1024     {
1025         var didChange = false;
1026
1027         var lowerBound;
1028         if (this.startAtZero)
1029             lowerBound = 0;
1030         else
1031             lowerBound = this._lowerBound(resource);
1032
1033         if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
1034             this.minimumBoundary = lowerBound;
1035             didChange = true;
1036         }
1037
1038         var upperBound = this._upperBound(resource);
1039         if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
1040             this.maximumBoundary = upperBound;
1041             didChange = true;
1042         }
1043
1044         return didChange;
1045     },
1046
1047     formatValue: function(value)
1048     {
1049         return Number.secondsToString(value, WebInspector.UIString);
1050     },
1051
1052     _lowerBound: function(resource)
1053     {
1054         return 0;
1055     },
1056
1057     _upperBound: function(resource)
1058     {
1059         return 0;
1060     }
1061 }
1062
1063 WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;
1064
1065 WebInspector.ResourceTransferTimeCalculator = function()
1066 {
1067     WebInspector.ResourceTimeCalculator.call(this, false);
1068 }
1069
1070 WebInspector.ResourceTransferTimeCalculator.prototype = {
1071     formatValue: function(value)
1072     {
1073         return Number.secondsToString(value, WebInspector.UIString);
1074     },
1075
1076     _lowerBound: function(resource)
1077     {
1078         return resource.startTime;
1079     },
1080
1081     _upperBound: function(resource)
1082     {
1083         return resource.endTime;
1084     }
1085 }
1086
1087 WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
1088
1089 WebInspector.ResourceTransferDurationCalculator = function()
1090 {
1091     WebInspector.ResourceTimeCalculator.call(this, true);
1092 }
1093
1094 WebInspector.ResourceTransferDurationCalculator.prototype = {
1095     formatValue: function(value)
1096     {
1097         return Number.secondsToString(value, WebInspector.UIString);
1098     },
1099
1100     _upperBound: function(resource)
1101     {
1102         return resource.duration;
1103     }
1104 }
1105
1106 WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
1107
1108 WebInspector.ResourceTransferSizeCalculator = function()
1109 {
1110     WebInspector.AbstractTimelineCalculator.call(this);
1111 }
1112
1113 WebInspector.ResourceTransferSizeCalculator.prototype = {
1114     computeBarGraphLabels: function(resource)
1115     {
1116         var networkBytes = this._networkBytes(resource);
1117         var resourceBytes = this._value(resource);
1118         if (networkBytes && networkBytes !== resourceBytes) {
1119             // Transferred size is not the same as reported resource length.
1120             var networkBytesString = this.formatValue(networkBytes);
1121             var left = networkBytesString;
1122             var right = this.formatValue(resourceBytes);
1123             var tooltip = right ? WebInspector.UIString("%s (%s transferred)", right, networkBytesString) : right;
1124         } else {
1125             var left = this.formatValue(resourceBytes);
1126             var right = left;
1127             var tooltip = left;
1128         }
1129         if (resource.cached)
1130             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1131         return {left: left, right: right, tooltip: tooltip};
1132     },
1133
1134     computeBarGraphPercentages: function(item)
1135     {
1136         const resourceBytesAsPercent = (this._value(item) / this.boundarySpan) * 100;
1137         const networkBytesAsPercent = this._networkBytes(item) ? (this._networkBytes(item) / this.boundarySpan) * 100 : resourceBytesAsPercent;
1138         return {start: 0, middle: networkBytesAsPercent, end: resourceBytesAsPercent};
1139     },
1140
1141     _value: function(resource)
1142     {
1143         return resource.resourceSize;
1144     },
1145
1146     _networkBytes: function(resource)
1147     {
1148         return resource.transferSize;
1149     },
1150
1151     formatValue: function(value)
1152     {
1153         return Number.bytesToString(value, WebInspector.UIString);
1154     }
1155 }
1156
1157 WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;
1158
1159 WebInspector.ResourceSidebarTreeElement = function(resource)
1160 {
1161     this.resource = resource;
1162
1163     this.createIconElement();
1164
1165     WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource);
1166
1167     this.refreshTitles();
1168 }
1169
1170 WebInspector.ResourceSidebarTreeElement.prototype = {
1171     onattach: function()
1172     {
1173         WebInspector.SidebarTreeElement.prototype.onattach.call(this);
1174
1175         this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
1176         this._listItemNode.draggable = true;
1177         
1178         // FIXME: should actually add handler to parent, to be resolved via
1179         // https://bugs.webkit.org/show_bug.cgi?id=30227
1180         this._listItemNode.addEventListener("dragstart", this.ondragstart.bind(this), false);
1181         this.updateErrorsAndWarnings();
1182     },
1183
1184     onselect: function()
1185     {
1186         WebInspector.panels.resources.showResource(this.resource);
1187     },
1188     
1189     ondblclick: function(event)
1190     {
1191         InjectedScriptAccess.getDefault().openInInspectedWindow(this.resource.url, function() {});
1192     },
1193
1194     ondragstart: function(event) {
1195         event.dataTransfer.setData("text/plain", this.resource.url);
1196         event.dataTransfer.setData("text/uri-list", this.resource.url + "\r\n");
1197         event.dataTransfer.effectAllowed = "copy";
1198         return true;
1199     },
1200
1201     get mainTitle()
1202     {
1203         return this.resource.displayName;
1204     },
1205
1206     set mainTitle(x)
1207     {
1208         // Do nothing.
1209     },
1210
1211     get subtitle()
1212     {
1213         var subtitle = this.resource.displayDomain;
1214
1215         if (this.resource.path && this.resource.lastPathComponent) {
1216             var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent);
1217             if (lastPathComponentIndex != -1)
1218                 subtitle += this.resource.path.substring(0, lastPathComponentIndex);
1219         }
1220
1221         return subtitle;
1222     },
1223
1224     set subtitle(x)
1225     {
1226         // Do nothing.
1227     },
1228
1229     get selectable()
1230     {
1231         return WebInspector.panels.resources.isCategoryVisible(this.resource.category.name);
1232     },
1233
1234     createIconElement: function()
1235     {
1236         var previousIconElement = this.iconElement;
1237
1238         if (this.resource.category === WebInspector.resourceCategories.images) {
1239             var previewImage = document.createElement("img");
1240             previewImage.className = "image-resource-icon-preview";
1241             previewImage.src = this.resource.url;
1242
1243             this.iconElement = document.createElement("div");
1244             this.iconElement.className = "icon";
1245             this.iconElement.appendChild(previewImage);
1246         } else {
1247             this.iconElement = document.createElement("img");
1248             this.iconElement.className = "icon";
1249         }
1250
1251         if (previousIconElement)
1252             previousIconElement.parentNode.replaceChild(this.iconElement, previousIconElement);
1253     },
1254
1255     refresh: function()
1256     {
1257         this.refreshTitles();
1258
1259         if (!this._listItemNode.hasStyleClass("resources-category-" + this.resource.category.name)) {
1260             this._listItemNode.removeMatchingStyleClasses("resources-category-\\w+");
1261             this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
1262
1263             this.createIconElement();
1264         }
1265
1266         this.tooltip = this.resource.url;
1267     },
1268
1269     resetBubble: function()
1270     {
1271         this.bubbleText = "";
1272         this.bubbleElement.removeStyleClass("search-matches");
1273         this.bubbleElement.removeStyleClass("warning");
1274         this.bubbleElement.removeStyleClass("error");
1275     },
1276
1277     set searchMatches(matches)
1278     {
1279         this.resetBubble();
1280
1281         if (!matches)
1282             return;
1283
1284         this.bubbleText = matches;
1285         this.bubbleElement.addStyleClass("search-matches");
1286     },
1287
1288     updateErrorsAndWarnings: function()
1289     {
1290         this.resetBubble();
1291
1292         if (this.resource.warnings || this.resource.errors)
1293             this.bubbleText = (this.resource.warnings + this.resource.errors);
1294
1295         if (this.resource.warnings)
1296             this.bubbleElement.addStyleClass("warning");
1297
1298         if (this.resource.errors)
1299             this.bubbleElement.addStyleClass("error");
1300     }
1301 }
1302
1303 WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b)
1304 {
1305     return WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1306         || WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
1307         || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
1308 }
1309
1310 WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b)
1311 {
1312     return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource)
1313         || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1314         || WebInspector.Resource.CompareByEndTime(a.resource, b.resource);
1315 }
1316
1317 WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b)
1318 {
1319     return WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
1320         || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1321         || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
1322 }
1323
1324 WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b)
1325 {
1326     return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource);
1327 }
1328
1329 WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b)
1330 {
1331     return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource);
1332 }
1333
1334 WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b)
1335 {
1336     return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource);
1337 }
1338
1339 WebInspector.ResourceSidebarTreeElement.CompareByDescendingTransferSize = function(a, b)
1340 {
1341     return -1 * WebInspector.Resource.CompareByTransferSize(a.resource, b.resource);
1342 }
1343
1344 WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
1345
1346 WebInspector.ResourceGraph = function(resource)
1347 {
1348     this.resource = resource;
1349
1350     this._graphElement = document.createElement("div");
1351     this._graphElement.className = "resources-graph-side";
1352     this._graphElement.addEventListener("mouseover", this.refreshLabelPositions.bind(this), false);
1353
1354     this._cachedChanged();
1355
1356     this._barAreaElement = document.createElement("div");
1357     this._barAreaElement.className = "resources-graph-bar-area hidden";
1358     this._barAreaElement.resource = resource;
1359     this._graphElement.appendChild(this._barAreaElement);
1360
1361     this._barLeftElement = document.createElement("div");
1362     this._barLeftElement.className = "resources-graph-bar waiting";
1363     this._barAreaElement.appendChild(this._barLeftElement);
1364
1365     this._barRightElement = document.createElement("div");
1366     this._barRightElement.className = "resources-graph-bar";
1367     this._barAreaElement.appendChild(this._barRightElement);
1368
1369     this._labelLeftElement = document.createElement("div");
1370     this._labelLeftElement.className = "resources-graph-label waiting";
1371     this._barAreaElement.appendChild(this._labelLeftElement);
1372
1373     this._labelRightElement = document.createElement("div");
1374     this._labelRightElement.className = "resources-graph-label";
1375     this._barAreaElement.appendChild(this._labelRightElement);
1376
1377     this._graphElement.addStyleClass("resources-category-" + resource.category.name);
1378
1379     resource.addEventListener("cached changed", this._cachedChanged, this);
1380 }
1381
1382 WebInspector.ResourceGraph.prototype = {
1383     get graphElement()
1384     {
1385         return this._graphElement;
1386     },
1387
1388     refreshLabelPositions: function()
1389     {
1390         this._labelLeftElement.style.removeProperty("left");
1391         this._labelLeftElement.style.removeProperty("right");
1392         this._labelLeftElement.removeStyleClass("before");
1393         this._labelLeftElement.removeStyleClass("hidden");
1394
1395         this._labelRightElement.style.removeProperty("left");
1396         this._labelRightElement.style.removeProperty("right");
1397         this._labelRightElement.removeStyleClass("after");
1398         this._labelRightElement.removeStyleClass("hidden");
1399
1400         const labelPadding = 10;
1401         const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
1402         const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;
1403
1404         if (this._isBarOpaqueAtLeft) {
1405             var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
1406             var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
1407         } else {
1408             var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
1409             var rightBarWidth = barRightElementOffsetWidth - labelPadding;
1410         }
1411
1412         const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
1413         const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;
1414
1415         const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
1416         const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
1417         const graphElementOffsetWidth = this._graphElement.offsetWidth;
1418
1419         if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
1420             var leftHidden = true;
1421
1422         if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
1423             var rightHidden = true;
1424
1425         if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
1426             // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
1427             if (labelBefore && !labelAfter)
1428                 leftHidden = true;
1429             else if (labelAfter && !labelBefore)
1430                 rightHidden = true;
1431         }
1432
1433         if (labelBefore) {
1434             if (leftHidden)
1435                 this._labelLeftElement.addStyleClass("hidden");
1436             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
1437             this._labelLeftElement.addStyleClass("before");
1438         } else {
1439             this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
1440             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
1441         }
1442
1443         if (labelAfter) {
1444             if (rightHidden)
1445                 this._labelRightElement.addStyleClass("hidden");
1446             this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
1447             this._labelRightElement.addStyleClass("after");
1448         } else {
1449             this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
1450             this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
1451         }
1452     },
1453
1454     refresh: function(calculator, isBarOpaqueAtLeft)
1455     {
1456         var percentages = calculator.computeBarGraphPercentages(this.resource);
1457         var labels = calculator.computeBarGraphLabels(this.resource);
1458
1459         this._percentages = percentages;
1460
1461         this._barAreaElement.removeStyleClass("hidden");
1462
1463         if (!this._graphElement.hasStyleClass("resources-category-" + this.resource.category.name)) {
1464             this._graphElement.removeMatchingStyleClasses("resources-category-\\w+");
1465             this._graphElement.addStyleClass("resources-category-" + this.resource.category.name);
1466         }
1467
1468         this._barLeftElement.style.setProperty("left", percentages.start + "%");
1469         this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
1470
1471         if (!isBarOpaqueAtLeft) {
1472             this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
1473             this._barRightElement.style.setProperty("left", percentages.middle + "%");
1474
1475             if (this._isBarOpaqueAtLeft != isBarOpaqueAtLeft) {
1476                 this._barLeftElement.addStyleClass("waiting");
1477                 this._barRightElement.removeStyleClass("waiting-right");
1478                 this._labelLeftElement.addStyleClass("waiting");
1479                 this._labelRightElement.removeStyleClass("waiting-right");
1480             }
1481         } else {
1482             this._barLeftElement.style.setProperty("right", (100 - percentages.middle) + "%");
1483             this._barRightElement.style.setProperty("left", percentages.start + "%");
1484
1485             if (this._isBarOpaqueAtLeft != isBarOpaqueAtLeft) {
1486                 this._barLeftElement.removeStyleClass("waiting");
1487                 this._barRightElement.addStyleClass("waiting-right");
1488                 this._labelLeftElement.removeStyleClass("waiting");
1489                 this._labelRightElement.addStyleClass("waiting-right");
1490             }
1491         }
1492
1493         this._isBarOpaqueAtLeft = isBarOpaqueAtLeft;
1494
1495         this._labelLeftElement.textContent = labels.left;
1496         this._labelRightElement.textContent = labels.right;
1497
1498         var tooltip = (labels.tooltip || "");
1499         this._barLeftElement.title = tooltip;
1500         this._labelLeftElement.title = tooltip;
1501         this._labelRightElement.title = tooltip;
1502         this._barRightElement.title = tooltip;
1503     },
1504
1505     _cachedChanged: function()
1506     {
1507         if (this.resource.cached)
1508             this._graphElement.addStyleClass("resource-cached");
1509     }
1510 }