2010-08-09 Ilya Tikhonovsky <loislo@chromium.org>
[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.ResourceTimeCalculator = function(startAtZero)
895 {
896     WebInspector.AbstractTimelineCalculator.call(this);
897     this.startAtZero = startAtZero;
898 }
899
900 WebInspector.ResourceTimeCalculator.prototype = {
901     computeSummaryValues: function(resources)
902     {
903         var resourcesByCategory = {};
904         var resourcesLength = resources.length;
905         for (var i = 0; i < resourcesLength; ++i) {
906             var resource = resources[i];
907             if (!(resource.category.name in resourcesByCategory))
908                 resourcesByCategory[resource.category.name] = [];
909             resourcesByCategory[resource.category.name].push(resource);
910         }
911
912         var earliestStart;
913         var latestEnd;
914         var categoryValues = {};
915         for (var category in resourcesByCategory) {
916             resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
917             categoryValues[category] = 0;
918
919             var segment = {start: -1, end: -1};
920
921             var categoryResources = resourcesByCategory[category];
922             var resourcesLength = categoryResources.length;
923             for (var i = 0; i < resourcesLength; ++i) {
924                 var resource = categoryResources[i];
925                 if (resource.startTime === -1 || resource.endTime === -1)
926                     continue;
927
928                 if (typeof earliestStart === "undefined")
929                     earliestStart = resource.startTime;
930                 else
931                     earliestStart = Math.min(earliestStart, resource.startTime);
932
933                 if (typeof latestEnd === "undefined")
934                     latestEnd = resource.endTime;
935                 else
936                     latestEnd = Math.max(latestEnd, resource.endTime);
937
938                 if (resource.startTime <= segment.end) {
939                     segment.end = Math.max(segment.end, resource.endTime);
940                     continue;
941                 }
942
943                 categoryValues[category] += segment.end - segment.start;
944
945                 segment.start = resource.startTime;
946                 segment.end = resource.endTime;
947             }
948
949             // Add the last segment
950             categoryValues[category] += segment.end - segment.start;
951         }
952
953         return {categoryValues: categoryValues, total: latestEnd - earliestStart};
954     },
955
956     computeBarGraphPercentages: function(resource)
957     {
958         if (resource.startTime !== -1)
959             var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
960         else
961             var start = 0;
962
963         if (resource.responseReceivedTime !== -1)
964             var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
965         else
966             var middle = (this.startAtZero ? start : 100);
967
968         if (resource.endTime !== -1)
969             var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
970         else
971             var end = (this.startAtZero ? middle : 100);
972
973         if (this.startAtZero) {
974             end -= start;
975             middle -= start;
976             start = 0;
977         }
978
979         return {start: start, middle: middle, end: end};
980     },
981     
982     computePercentageFromEventTime: function(eventTime)
983     {
984         // This function computes a percentage in terms of the total loading time
985         // of a specific event. If startAtZero is set, then this is useless, and we
986         // want to return 0.
987         if (eventTime !== -1 && !this.startAtZero)
988             return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;
989
990         return 0;
991     },
992
993     computeBarGraphLabels: function(resource)
994     {
995         var rightLabel = "";
996         if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
997             rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
998
999         var hasLatency = resource.latency > 0;
1000         if (hasLatency)
1001             var leftLabel = this.formatValue(resource.latency);
1002         else
1003             var leftLabel = rightLabel;
1004
1005         if (resource.timing)
1006             return {left: leftLabel, right: rightLabel};
1007
1008         if (hasLatency && rightLabel) {
1009             var total = this.formatValue(resource.duration);
1010             var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
1011         } else if (hasLatency)
1012             var tooltip = WebInspector.UIString("%s latency", leftLabel);
1013         else if (rightLabel)
1014             var tooltip = WebInspector.UIString("%s download", rightLabel);
1015
1016         if (resource.cached)
1017             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1018         return {left: leftLabel, right: rightLabel, tooltip: tooltip};
1019     },
1020
1021     updateBoundaries: function(resource)
1022     {
1023         var didChange = false;
1024
1025         var lowerBound;
1026         if (this.startAtZero)
1027             lowerBound = 0;
1028         else
1029             lowerBound = this._lowerBound(resource);
1030
1031         if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
1032             this.minimumBoundary = lowerBound;
1033             didChange = true;
1034         }
1035
1036         var upperBound = this._upperBound(resource);
1037         if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
1038             this.maximumBoundary = upperBound;
1039             didChange = true;
1040         }
1041
1042         return didChange;
1043     },
1044
1045     formatValue: function(value)
1046     {
1047         return Number.secondsToString(value, WebInspector.UIString);
1048     },
1049
1050     _lowerBound: function(resource)
1051     {
1052         return 0;
1053     },
1054
1055     _upperBound: function(resource)
1056     {
1057         return 0;
1058     }
1059 }
1060
1061 WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;
1062
1063 WebInspector.ResourceTransferTimeCalculator = function()
1064 {
1065     WebInspector.ResourceTimeCalculator.call(this, false);
1066 }
1067
1068 WebInspector.ResourceTransferTimeCalculator.prototype = {
1069     formatValue: function(value)
1070     {
1071         return Number.secondsToString(value, WebInspector.UIString);
1072     },
1073
1074     _lowerBound: function(resource)
1075     {
1076         return resource.startTime;
1077     },
1078
1079     _upperBound: function(resource)
1080     {
1081         return resource.endTime;
1082     }
1083 }
1084
1085 WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
1086
1087 WebInspector.ResourceTransferDurationCalculator = function()
1088 {
1089     WebInspector.ResourceTimeCalculator.call(this, true);
1090 }
1091
1092 WebInspector.ResourceTransferDurationCalculator.prototype = {
1093     formatValue: function(value)
1094     {
1095         return Number.secondsToString(value, WebInspector.UIString);
1096     },
1097
1098     _upperBound: function(resource)
1099     {
1100         return resource.duration;
1101     }
1102 }
1103
1104 WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
1105
1106 WebInspector.ResourceTransferSizeCalculator = function()
1107 {
1108     WebInspector.AbstractTimelineCalculator.call(this);
1109 }
1110
1111 WebInspector.ResourceTransferSizeCalculator.prototype = {
1112     computeBarGraphLabels: function(resource)
1113     {
1114         var networkBytes = this._networkBytes(resource);
1115         var resourceBytes = this._value(resource);
1116         if (networkBytes && networkBytes !== resourceBytes) {
1117             // Transferred size is not the same as reported resource length.
1118             var networkBytesString = this.formatValue(networkBytes);
1119             var left = networkBytesString;
1120             var right = this.formatValue(resourceBytes);
1121             var tooltip = right ? WebInspector.UIString("%s (%s transferred)", right, networkBytesString) : right;
1122         } else {
1123             var left = this.formatValue(resourceBytes);
1124             var right = left;
1125             var tooltip = left;
1126         }
1127         if (resource.cached)
1128             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1129         return {left: left, right: right, tooltip: tooltip};
1130     },
1131
1132     computeBarGraphPercentages: function(item)
1133     {
1134         const resourceBytesAsPercent = (this._value(item) / this.boundarySpan) * 100;
1135         const networkBytesAsPercent = this._networkBytes(item) ? (this._networkBytes(item) / this.boundarySpan) * 100 : resourceBytesAsPercent;
1136         return {start: 0, middle: networkBytesAsPercent, end: resourceBytesAsPercent};
1137     },
1138
1139     _value: function(resource)
1140     {
1141         return resource.resourceSize;
1142     },
1143
1144     _networkBytes: function(resource)
1145     {
1146         return resource.transferSize;
1147     },
1148
1149     formatValue: function(value)
1150     {
1151         return Number.bytesToString(value, WebInspector.UIString);
1152     }
1153 }
1154
1155 WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;
1156
1157 WebInspector.ResourceSidebarTreeElement = function(resource)
1158 {
1159     this.resource = resource;
1160
1161     this.createIconElement();
1162
1163     WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource);
1164
1165     this.refreshTitles();
1166 }
1167
1168 WebInspector.ResourceSidebarTreeElement.prototype = {
1169     onattach: function()
1170     {
1171         WebInspector.SidebarTreeElement.prototype.onattach.call(this);
1172
1173         this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
1174         this._listItemNode.draggable = true;
1175         
1176         // FIXME: should actually add handler to parent, to be resolved via
1177         // https://bugs.webkit.org/show_bug.cgi?id=30227
1178         this._listItemNode.addEventListener("dragstart", this.ondragstart.bind(this), false);
1179         this.updateErrorsAndWarnings();
1180     },
1181
1182     onselect: function()
1183     {
1184         WebInspector.panels.resources.showResource(this.resource);
1185     },
1186     
1187     ondblclick: function(event)
1188     {
1189         InjectedScriptAccess.getDefault().openInInspectedWindow(this.resource.url, function() {});
1190     },
1191
1192     ondragstart: function(event) {
1193         event.dataTransfer.setData("text/plain", this.resource.url);
1194         event.dataTransfer.setData("text/uri-list", this.resource.url + "\r\n");
1195         event.dataTransfer.effectAllowed = "copy";
1196         return true;
1197     },
1198
1199     get mainTitle()
1200     {
1201         return this.resource.displayName;
1202     },
1203
1204     set mainTitle(x)
1205     {
1206         // Do nothing.
1207     },
1208
1209     get subtitle()
1210     {
1211         var subtitle = this.resource.displayDomain;
1212
1213         if (this.resource.path && this.resource.lastPathComponent) {
1214             var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent);
1215             if (lastPathComponentIndex != -1)
1216                 subtitle += this.resource.path.substring(0, lastPathComponentIndex);
1217         }
1218
1219         return subtitle;
1220     },
1221
1222     set subtitle(x)
1223     {
1224         // Do nothing.
1225     },
1226
1227     get selectable()
1228     {
1229         return WebInspector.panels.resources.isCategoryVisible(this.resource.category.name);
1230     },
1231
1232     createIconElement: function()
1233     {
1234         var previousIconElement = this.iconElement;
1235
1236         if (this.resource.category === WebInspector.resourceCategories.images) {
1237             var previewImage = document.createElement("img");
1238             previewImage.className = "image-resource-icon-preview";
1239             previewImage.src = this.resource.url;
1240
1241             this.iconElement = document.createElement("div");
1242             this.iconElement.className = "icon";
1243             this.iconElement.appendChild(previewImage);
1244         } else {
1245             this.iconElement = document.createElement("img");
1246             this.iconElement.className = "icon";
1247         }
1248
1249         if (previousIconElement)
1250             previousIconElement.parentNode.replaceChild(this.iconElement, previousIconElement);
1251     },
1252
1253     refresh: function()
1254     {
1255         this.refreshTitles();
1256
1257         if (!this._listItemNode.hasStyleClass("resources-category-" + this.resource.category.name)) {
1258             this._listItemNode.removeMatchingStyleClasses("resources-category-\\w+");
1259             this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
1260
1261             this.createIconElement();
1262         }
1263
1264         this.tooltip = this.resource.url;
1265     },
1266
1267     resetBubble: function()
1268     {
1269         this.bubbleText = "";
1270         this.bubbleElement.removeStyleClass("search-matches");
1271         this.bubbleElement.removeStyleClass("warning");
1272         this.bubbleElement.removeStyleClass("error");
1273     },
1274
1275     set searchMatches(matches)
1276     {
1277         this.resetBubble();
1278
1279         if (!matches)
1280             return;
1281
1282         this.bubbleText = matches;
1283         this.bubbleElement.addStyleClass("search-matches");
1284     },
1285
1286     updateErrorsAndWarnings: function()
1287     {
1288         this.resetBubble();
1289
1290         if (this.resource.warnings || this.resource.errors)
1291             this.bubbleText = (this.resource.warnings + this.resource.errors);
1292
1293         if (this.resource.warnings)
1294             this.bubbleElement.addStyleClass("warning");
1295
1296         if (this.resource.errors)
1297             this.bubbleElement.addStyleClass("error");
1298     }
1299 }
1300
1301 WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b)
1302 {
1303     return WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1304         || WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
1305         || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
1306 }
1307
1308 WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b)
1309 {
1310     return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource)
1311         || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1312         || WebInspector.Resource.CompareByEndTime(a.resource, b.resource);
1313 }
1314
1315 WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b)
1316 {
1317     return WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
1318         || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1319         || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
1320 }
1321
1322 WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b)
1323 {
1324     return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource);
1325 }
1326
1327 WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b)
1328 {
1329     return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource);
1330 }
1331
1332 WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b)
1333 {
1334     return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource);
1335 }
1336
1337 WebInspector.ResourceSidebarTreeElement.CompareByDescendingTransferSize = function(a, b)
1338 {
1339     return -1 * WebInspector.Resource.CompareByTransferSize(a.resource, b.resource);
1340 }
1341
1342 WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
1343
1344 WebInspector.ResourceGraph = function(resource)
1345 {
1346     this.resource = resource;
1347
1348     this._graphElement = document.createElement("div");
1349     this._graphElement.className = "resources-graph-side";
1350     this._graphElement.addEventListener("mouseover", this.refreshLabelPositions.bind(this), false);
1351
1352     this._cachedChanged();
1353
1354     this._barAreaElement = document.createElement("div");
1355     this._barAreaElement.className = "resources-graph-bar-area hidden";
1356     this._barAreaElement.resource = resource;
1357     this._graphElement.appendChild(this._barAreaElement);
1358
1359     this._barLeftElement = document.createElement("div");
1360     this._barLeftElement.className = "resources-graph-bar waiting";
1361     this._barAreaElement.appendChild(this._barLeftElement);
1362
1363     this._barRightElement = document.createElement("div");
1364     this._barRightElement.className = "resources-graph-bar";
1365     this._barAreaElement.appendChild(this._barRightElement);
1366
1367     this._labelLeftElement = document.createElement("div");
1368     this._labelLeftElement.className = "resources-graph-label waiting";
1369     this._barAreaElement.appendChild(this._labelLeftElement);
1370
1371     this._labelRightElement = document.createElement("div");
1372     this._labelRightElement.className = "resources-graph-label";
1373     this._barAreaElement.appendChild(this._labelRightElement);
1374
1375     this._graphElement.addStyleClass("resources-category-" + resource.category.name);
1376
1377     resource.addEventListener("cached changed", this._cachedChanged, this);
1378 }
1379
1380 WebInspector.ResourceGraph.prototype = {
1381     get graphElement()
1382     {
1383         return this._graphElement;
1384     },
1385
1386     refreshLabelPositions: function()
1387     {
1388         this._labelLeftElement.style.removeProperty("left");
1389         this._labelLeftElement.style.removeProperty("right");
1390         this._labelLeftElement.removeStyleClass("before");
1391         this._labelLeftElement.removeStyleClass("hidden");
1392
1393         this._labelRightElement.style.removeProperty("left");
1394         this._labelRightElement.style.removeProperty("right");
1395         this._labelRightElement.removeStyleClass("after");
1396         this._labelRightElement.removeStyleClass("hidden");
1397
1398         const labelPadding = 10;
1399         const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
1400         const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;
1401
1402         if (this._isBarOpaqueAtLeft) {
1403             var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
1404             var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
1405         } else {
1406             var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
1407             var rightBarWidth = barRightElementOffsetWidth - labelPadding;
1408         }
1409
1410         const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
1411         const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;
1412
1413         const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
1414         const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
1415         const graphElementOffsetWidth = this._graphElement.offsetWidth;
1416
1417         if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
1418             var leftHidden = true;
1419
1420         if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
1421             var rightHidden = true;
1422
1423         if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
1424             // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
1425             if (labelBefore && !labelAfter)
1426                 leftHidden = true;
1427             else if (labelAfter && !labelBefore)
1428                 rightHidden = true;
1429         }
1430
1431         if (labelBefore) {
1432             if (leftHidden)
1433                 this._labelLeftElement.addStyleClass("hidden");
1434             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
1435             this._labelLeftElement.addStyleClass("before");
1436         } else {
1437             this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
1438             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
1439         }
1440
1441         if (labelAfter) {
1442             if (rightHidden)
1443                 this._labelRightElement.addStyleClass("hidden");
1444             this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
1445             this._labelRightElement.addStyleClass("after");
1446         } else {
1447             this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
1448             this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
1449         }
1450     },
1451
1452     refresh: function(calculator, isBarOpaqueAtLeft)
1453     {
1454         var percentages = calculator.computeBarGraphPercentages(this.resource);
1455         var labels = calculator.computeBarGraphLabels(this.resource);
1456
1457         this._percentages = percentages;
1458
1459         this._barAreaElement.removeStyleClass("hidden");
1460
1461         if (!this._graphElement.hasStyleClass("resources-category-" + this.resource.category.name)) {
1462             this._graphElement.removeMatchingStyleClasses("resources-category-\\w+");
1463             this._graphElement.addStyleClass("resources-category-" + this.resource.category.name);
1464         }
1465
1466         this._barLeftElement.style.setProperty("left", percentages.start + "%");
1467         this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
1468
1469         if (!isBarOpaqueAtLeft) {
1470             this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
1471             this._barRightElement.style.setProperty("left", percentages.middle + "%");
1472
1473             if (this._isBarOpaqueAtLeft != isBarOpaqueAtLeft) {
1474                 this._barLeftElement.addStyleClass("waiting");
1475                 this._barRightElement.removeStyleClass("waiting-right");
1476                 this._labelLeftElement.addStyleClass("waiting");
1477                 this._labelRightElement.removeStyleClass("waiting-right");
1478             }
1479         } else {
1480             this._barLeftElement.style.setProperty("right", (100 - percentages.middle) + "%");
1481             this._barRightElement.style.setProperty("left", percentages.start + "%");
1482
1483             if (this._isBarOpaqueAtLeft != isBarOpaqueAtLeft) {
1484                 this._barLeftElement.removeStyleClass("waiting");
1485                 this._barRightElement.addStyleClass("waiting-right");
1486                 this._labelLeftElement.removeStyleClass("waiting");
1487                 this._labelRightElement.addStyleClass("waiting-right");
1488             }
1489         }
1490
1491         this._isBarOpaqueAtLeft = isBarOpaqueAtLeft;
1492
1493         this._labelLeftElement.textContent = labels.left;
1494         this._labelRightElement.textContent = labels.right;
1495
1496         var tooltip = (labels.tooltip || "");
1497         this._barLeftElement.title = tooltip;
1498         this._labelLeftElement.title = tooltip;
1499         this._labelRightElement.title = tooltip;
1500         this._barRightElement.title = tooltip;
1501     },
1502
1503     _cachedChanged: function()
1504     {
1505         if (this.resource.cached)
1506             this._graphElement.addStyleClass("resource-cached");
1507     }
1508 }