61a6d6258df514752492d30f099e5bf461d8bd0d
[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);
33
34     this.element.addStyleClass("resources");
35
36     this._createPanelEnabler();
37
38     this.viewsContainerElement = document.createElement("div");
39     this.viewsContainerElement.id = "resource-views";
40     this.element.appendChild(this.viewsContainerElement);
41
42     this.createFilterPanel();
43     this.createInterface();
44
45     this._createStatusbarButtons();
46
47     this.reset();
48     this.filter(this.filterAllElement, false);
49     this.graphsTreeElement.children[0].select();
50 }
51
52 WebInspector.ResourcesPanel.prototype = {
53     toolbarItemClass: "resources",
54
55     get toolbarItemLabel()
56     {
57         return WebInspector.UIString("Resources");
58     },
59
60     get statusBarItems()
61     {
62         return [this.enableToggleButton.element, this.largerResourcesButton.element, this.sortingSelectElement];
63     },
64
65     get categories()
66     {
67         return WebInspector.resourceCategories;
68     },
69
70     createItemTreeElement: function(item)
71     {
72         return new WebInspector.ResourceSidebarTreeElement(item);
73     },
74
75     createItemGraph: function(item)
76     {
77         return new WebInspector.ResourceGraph(item);
78     },
79
80     isCategoryVisible: function(categoryName)
81     {
82         return (this.itemsGraphsElement.hasStyleClass("filter-all") || this.itemsGraphsElement.hasStyleClass("filter-" + categoryName.toLowerCase()));
83     },
84
85     populateSidebar: function()
86     {
87         var timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time"));
88         timeGraphItem.onselect = this._graphSelected.bind(this);
89
90         var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator();
91         var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator();
92
93         timeGraphItem.sortingOptions = [
94             { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator },
95             { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator },
96             { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator },
97             { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator },
98             { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator },
99         ];
100
101         timeGraphItem.selectedSortingOptionIndex = 1;
102
103         var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size"));
104         sizeGraphItem.onselect = this._graphSelected.bind(this);
105
106         var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator();
107         sizeGraphItem.sortingOptions = [
108             { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator },
109         ];
110
111         sizeGraphItem.selectedSortingOptionIndex = 0;
112
113         this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true);
114         this.sidebarTree.appendChild(this.graphsTreeElement);
115
116         this.graphsTreeElement.appendChild(timeGraphItem);
117         this.graphsTreeElement.appendChild(sizeGraphItem);
118         this.graphsTreeElement.expand();
119
120         this.itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true);
121         this.sidebarTree.appendChild(this.itemsTreeElement);
122
123         this.itemsTreeElement.expand();
124     },
125
126     _createPanelEnabler: function()
127     {
128         var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel.");
129         var panelEnablerDisclaimer = WebInspector.UIString("Enabling resource tracking will reload the page and make page loading slower.");
130         var panelEnablerButton = WebInspector.UIString("Enable resource tracking");
131
132         this.panelEnablerView = new WebInspector.PanelEnablerView("resources", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
133         this.panelEnablerView.addEventListener("enable clicked", this._enableResourceTracking, this);
134
135         this.element.appendChild(this.panelEnablerView.element);
136
137         this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
138         this.enableToggleButton.addEventListener("click", this._toggleResourceTracking.bind(this), false);
139     },
140
141     _createStatusbarButtons: function()
142     {
143         this.largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "resources-larger-resources-status-bar-item");
144         this.largerResourcesButton.toggled = Preferences.resourcesLargeRows;
145         this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
146         if (!Preferences.resourcesLargeRows) {
147             Preferences.resourcesLargeRows = !Preferences.resourcesLargeRows;
148             this._toggleLargerResources(); // this will toggle the preference back to the original
149         }
150
151         this.sortingSelectElement = document.createElement("select");
152         this.sortingSelectElement.className = "status-bar-item";
153         this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false);
154     },
155
156     get mainResourceLoadTime()
157     {
158         return this._mainResourceLoadTime || -1;
159     },
160     
161     set mainResourceLoadTime(x)
162     {
163         if (this._mainResourceLoadTime === x)
164             return;
165         
166         this._mainResourceLoadTime = x;
167         
168         // Update the dividers to draw the new line
169         this.updateGraphDividersIfNeeded(true);
170     },
171     
172     get mainResourceDOMContentTime()
173     {
174         return this._mainResourceDOMContentTime || -1;
175     },
176     
177     set mainResourceDOMContentTime(x)
178     {
179         if (this._mainResourceDOMContentTime === x)
180             return;
181         
182         this._mainResourceDOMContentTime = x;
183         
184         this.updateGraphDividersIfNeeded(true);
185     },
186
187     show: function()
188     {
189         WebInspector.AbstractTimelinePanel.prototype.show.call(this);
190
191         var visibleView = this.visibleView;
192         if (visibleView) {
193             visibleView.headersVisible = true;
194             visibleView.show(this.viewsContainerElement);
195         }
196
197         // Hide any views that are visible that are not this panel's current visible view.
198         // This can happen when a ResourceView is visible in the Scripts panel then switched
199         // to the this panel.
200         var resourcesLength = this._resources.length;
201         for (var i = 0; i < resourcesLength; ++i) {
202             var resource = this._resources[i];
203             var view = resource._resourcesView;
204             if (!view || view === visibleView)
205                 continue;
206             view.visible = false;
207         }
208     },
209
210     resize: function()
211     {
212         WebInspector.AbstractTimelinePanel.prototype.resize.call(this);
213
214         var visibleView = this.visibleView;
215         if (visibleView && "resize" in visibleView)
216             visibleView.resize();
217     },
218
219     get searchableViews()
220     {
221         var views = [];
222
223         const visibleView = this.visibleView;
224         if (visibleView && visibleView.performSearch)
225             views.push(visibleView);
226
227         var resourcesLength = this._resources.length;
228         for (var i = 0; i < resourcesLength; ++i) {
229             var resource = this._resources[i];
230             if (!resource._itemsTreeElement)
231                 continue;
232             var resourceView = this.resourceViewForResource(resource);
233             if (!resourceView.performSearch || resourceView === visibleView)
234                 continue;
235             views.push(resourceView);
236         }
237
238         return views;
239     },
240
241     get searchResultsSortFunction()
242     {
243         const resourceTreeElementSortFunction = this.sortingFunction;
244
245         function sortFuction(a, b)
246         {
247             return resourceTreeElementSortFunction(a.resource._itemsTreeElement, b.resource._itemsTreeElement);
248         }
249
250         return sortFuction;
251     },
252
253     searchMatchFound: function(view, matches)
254     {
255         view.resource._itemsTreeElement.searchMatches = matches;
256     },
257
258     searchCanceled: function(startingNewSearch)
259     {
260         WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
261
262         if (startingNewSearch || !this._resources)
263             return;
264
265         for (var i = 0; i < this._resources.length; ++i) {
266             var resource = this._resources[i];
267             if (resource._itemsTreeElement)
268                 resource._itemsTreeElement.updateErrorsAndWarnings();
269         }
270     },
271
272     performSearch: function(query)
273     {
274         for (var i = 0; i < this._resources.length; ++i) {
275             var resource = this._resources[i];
276             if (resource._itemsTreeElement)
277                 resource._itemsTreeElement.resetBubble();
278         }
279
280         WebInspector.Panel.prototype.performSearch.call(this, query);
281     },
282
283     get visibleView()
284     {
285         if (this.visibleResource)
286             return this.visibleResource._resourcesView;
287         return null;
288     },
289
290     get sortingFunction()
291     {
292         return this._sortingFunction;
293     },
294
295     set sortingFunction(x)
296     {
297         this._sortingFunction = x;
298         this._sortResourcesIfNeeded();
299     },
300
301     refresh: function()
302     {
303         WebInspector.AbstractTimelinePanel.prototype.refresh.call(this);
304
305         this._sortResourcesIfNeeded();
306         this._updateSummaryGraph();
307     },
308
309     _updateSummaryGraph: function()
310     {
311         this.summaryBar.update(this._resources);
312     },
313
314     resourceTrackingWasEnabled: function()
315     {
316         this.reset();
317     },
318
319     resourceTrackingWasDisabled: function()
320     {
321         this.reset();
322     },
323
324     reset: function()
325     {
326         this.closeVisibleResource();
327
328         delete this.currentQuery;
329         this.searchCanceled();
330
331         if (this._resources) {
332             var resourcesLength = this._resources.length;
333             for (var i = 0; i < resourcesLength; ++i) {
334                 var resource = this._resources[i];
335
336                 resource.warnings = 0;
337                 resource.errors = 0;
338
339                 delete resource._resourcesView;
340             }
341         }
342
343         WebInspector.AbstractTimelinePanel.prototype.reset.call(this);
344         
345         this.mainResourceLoadTime = -1;
346         this.mainResourceDOMContentTime = -1;
347  
348         this.viewsContainerElement.removeChildren();
349
350         this.summaryBar.reset();
351
352         if (InspectorController.resourceTrackingEnabled()) {
353             this.enableToggleButton.title = WebInspector.UIString("Resource tracking enabled. Click to disable.");
354             this.enableToggleButton.toggled = true;
355             this.largerResourcesButton.visible = true;
356             this.sortingSelectElement.removeStyleClass("hidden");
357             this.panelEnablerView.visible = false;
358         } else {
359             this.enableToggleButton.title = WebInspector.UIString("Resource tracking disabled. Click to enable.");
360             this.enableToggleButton.toggled = false;
361             this.largerResourcesButton.visible = false;
362             this.sortingSelectElement.addStyleClass("hidden");
363             this.panelEnablerView.visible = true;
364         }
365     },
366
367     addResource: function(resource)
368     {
369         this._resources.push(resource);
370         this.refreshResource(resource);
371     },
372
373     removeResource: function(resource)
374     {
375         if (this.visibleView === resource._resourcesView)
376             this.closeVisibleResource();
377
378         this.removeItem(resource);
379
380         resource.warnings = 0;
381         resource.errors = 0;
382
383         delete resource._resourcesView;
384     },
385
386     addMessageToResource: function(resource, msg)
387     {
388         if (!resource)
389             return;
390
391         switch (msg.level) {
392         case WebInspector.ConsoleMessage.MessageLevel.Warning:
393             resource.warnings += msg.repeatDelta;
394             break;
395         case WebInspector.ConsoleMessage.MessageLevel.Error:
396             resource.errors += msg.repeatDelta;
397             break;
398         }
399
400         if (!this.currentQuery && resource._itemsTreeElement)
401             resource._itemsTreeElement.updateErrorsAndWarnings();
402
403         var view = this.resourceViewForResource(resource);
404         if (view.addMessage)
405             view.addMessage(msg);
406     },
407
408     clearMessages: function()
409     {
410         var resourcesLength = this._resources.length;
411         for (var i = 0; i < resourcesLength; ++i) {
412             var resource = this._resources[i];
413             resource.warnings = 0;
414             resource.errors = 0;
415
416             if (!this.currentQuery && resource._itemsTreeElement)
417                 resource._itemsTreeElement.updateErrorsAndWarnings();
418
419             var view = resource._resourcesView;
420             if (!view || !view.clearMessages)
421                 continue;
422             view.clearMessages();
423         }
424     },
425
426     refreshResource: function(resource)
427     {
428         this.refreshItem(resource);
429     },
430
431     recreateViewForResourceIfNeeded: function(resource)
432     {
433         if (!resource || !resource._resourcesView)
434             return;
435
436         var newView = this._createResourceView(resource);
437         if (newView.prototype === resource._resourcesView.prototype)
438             return;
439
440         resource.warnings = 0;
441         resource.errors = 0;
442
443         if (!this.currentQuery && resource._itemsTreeElement)
444             resource._itemsTreeElement.updateErrorsAndWarnings();
445
446         var oldView = resource._resourcesView;
447
448         resource._resourcesView.detach();
449         delete resource._resourcesView;
450
451         resource._resourcesView = newView;
452
453         newView.headersVisible = oldView.headersVisible;
454
455         if (oldView.visible && oldView.element.parentNode)
456             newView.show(oldView.element.parentNode);
457     },
458
459     showResource: function(resource, line)
460     {
461         if (!resource)
462             return;
463
464         this.containerElement.addStyleClass("viewing-resource");
465
466         if (this.visibleResource && this.visibleResource._resourcesView)
467             this.visibleResource._resourcesView.hide();
468
469         var view = this.resourceViewForResource(resource);
470         view.headersVisible = true;
471         view.show(this.viewsContainerElement);
472
473         if (line) {
474             if (view.revealLine)
475                 view.revealLine(line);
476             if (view.highlightLine)
477                 view.highlightLine(line);
478         }
479
480         this.revealAndSelectItem(resource);
481
482         this.visibleResource = resource;
483
484         this.updateSidebarWidth();
485     },
486
487     showView: function(view)
488     {
489         if (!view)
490             return;
491         this.showResource(view.resource);
492     },
493
494     closeVisibleResource: function()
495     {
496         this.containerElement.removeStyleClass("viewing-resource");
497         this._updateDividersLabelBarPosition();
498
499         if (this.visibleResource && this.visibleResource._resourcesView)
500             this.visibleResource._resourcesView.hide();
501         delete this.visibleResource;
502
503         if (this._lastSelectedGraphTreeElement)
504             this._lastSelectedGraphTreeElement.select(true);
505
506         this.updateSidebarWidth();
507     },
508
509     resourceViewForResource: function(resource)
510     {
511         if (!resource)
512             return null;
513         if (!resource._resourcesView)
514             resource._resourcesView = this._createResourceView(resource);
515         return resource._resourcesView;
516     },
517
518     sourceFrameForResource: function(resource)
519     {
520         var view = this.resourceViewForResource(resource);
521         if (!view)
522             return null;
523
524         if (!view.setupSourceFrameIfNeeded)
525             return null;
526
527         // Setting up the source frame requires that we be attached.
528         if (!this.element.parentNode)
529             this.attach();
530
531         view.setupSourceFrameIfNeeded();
532         return view.sourceFrame;
533     },
534
535     _sortResourcesIfNeeded: function()
536     {
537         this.sortItems(this.sortingFunction);
538     },
539
540     updateGraphDividersIfNeeded: function(force)
541     {
542         var proceed = WebInspector.AbstractTimelinePanel.prototype.updateGraphDividersIfNeeded.call(this, force);
543         
544         if (!proceed)
545             return;
546
547         if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) {
548             // If our current sorting method starts at zero, that means it shows all
549             // resources starting at the same point, and so onLoad event and DOMContent
550             // event lines really wouldn't make much sense here, so don't render them.
551             // Additionally, if the calculator doesn't have the computePercentageFromEventTime
552             // function defined, we are probably sorting by size, and event times aren't relevant
553             // in this case.
554             return;
555         }
556
557         if (this.mainResourceLoadTime !== -1) {
558             var percent = this.calculator.computePercentageFromEventTime(this.mainResourceLoadTime);
559
560             var loadDivider = document.createElement("div");
561             loadDivider.className = "resources-onload-divider";
562
563             var loadDividerPadding = document.createElement("div");
564             loadDividerPadding.className = "resources-event-divider-padding";
565             loadDividerPadding.style.left = percent + "%";
566             loadDividerPadding.title = WebInspector.UIString("Load event fired");
567             loadDividerPadding.appendChild(loadDivider);
568
569             this.addEventDivider(loadDividerPadding);
570         }
571         
572         if (this.mainResourceDOMContentTime !== -1) {
573             var percent = this.calculator.computePercentageFromEventTime(this.mainResourceDOMContentTime);
574
575             var domContentDivider = document.createElement("div");
576             domContentDivider.className = "resources-ondomcontent-divider";
577             
578             var domContentDividerPadding = document.createElement("div");
579             domContentDividerPadding.className = "resources-event-divider-padding";
580             domContentDividerPadding.style.left = percent + "%";
581             domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired");
582             domContentDividerPadding.appendChild(domContentDivider);
583
584             this.addEventDivider(domContentDividerPadding);
585         }
586     },
587
588     _graphSelected: function(treeElement)
589     {
590         if (this._lastSelectedGraphTreeElement)
591             this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = this.sortingSelectElement.selectedIndex;
592
593         this._lastSelectedGraphTreeElement = treeElement;
594
595         this.sortingSelectElement.removeChildren();
596         for (var i = 0; i < treeElement.sortingOptions.length; ++i) {
597             var sortingOption = treeElement.sortingOptions[i];
598             var option = document.createElement("option");
599             option.label = sortingOption.name;
600             option.sortingFunction = sortingOption.sortingFunction;
601             option.calculator = sortingOption.calculator;
602             this.sortingSelectElement.appendChild(option);
603         }
604
605         this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex;
606         this._changeSortingFunction();
607
608         this.closeVisibleResource();
609         this.containerElement.scrollTop = 0;
610     },
611
612     _toggleLargerResources: function()
613     {
614         if (!this.itemsTreeElement._childrenListNode)
615             return;
616
617         this.itemsTreeElement.smallChildren = !this.itemsTreeElement.smallChildren;
618         Preferences.resourcesLargeRows = !Preferences.resourcesLargeRows;
619         InspectorController.setSetting("resources-large-rows", Preferences.resourcesLargeRows);
620
621         if (this.itemsTreeElement.smallChildren) {
622             this.itemsGraphsElement.addStyleClass("small");
623             this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
624             this.largerResourcesButton.toggled = false;
625             this.adjustScrollPosition();
626         } else {
627             this.itemsGraphsElement.removeStyleClass("small");
628             this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
629             this.largerResourcesButton.toggled = true;
630         }
631     },
632
633     _changeSortingFunction: function()
634     {
635         var selectedOption = this.sortingSelectElement[this.sortingSelectElement.selectedIndex];
636         this.sortingFunction = selectedOption.sortingFunction;
637         this.calculator = this.summaryBar.calculator = selectedOption.calculator;
638     },
639
640     _createResourceView: function(resource)
641     {
642         switch (resource.category) {
643             case WebInspector.resourceCategories.documents:
644             case WebInspector.resourceCategories.stylesheets:
645             case WebInspector.resourceCategories.scripts:
646             case WebInspector.resourceCategories.xhr:
647                 return new WebInspector.SourceView(resource);
648             case WebInspector.resourceCategories.images:
649                 return new WebInspector.ImageView(resource);
650             case WebInspector.resourceCategories.fonts:
651                 return new WebInspector.FontView(resource);
652             default:
653                 return new WebInspector.ResourceView(resource);
654         }
655     },
656
657     setSidebarWidth: function(width)
658     {
659         if (this.visibleResource) {
660             this.containerElement.style.width = width + "px";
661             this.sidebarElement.style.removeProperty("width");
662         } else {
663             this.sidebarElement.style.width = width + "px";
664             this.containerElement.style.removeProperty("width");
665         }
666
667         this.sidebarResizeElement.style.left = (width - 3) + "px";
668     },
669
670     updateMainViewWidth: function(width)
671     {
672         WebInspector.AbstractTimelinePanel.prototype.updateMainViewWidth.call(this, width);
673         this.viewsContainerElement.style.left = width + "px";
674     },
675
676     _enableResourceTracking: function()
677     {
678         if (InspectorController.resourceTrackingEnabled())
679             return;
680         this._toggleResourceTracking(this.panelEnablerView.alwaysEnabled);
681     },
682
683     _toggleResourceTracking: function(optionalAlways)
684     {
685         if (InspectorController.resourceTrackingEnabled()) {
686             this.largerResourcesButton.visible = false;
687             this.sortingSelectElement.visible = false;
688             InspectorController.disableResourceTracking(true);
689         } else {
690             this.largerResourcesButton.visible = true;
691             this.sortingSelectElement.visible = true;
692             InspectorController.enableResourceTracking(!!optionalAlways);
693         }
694     },
695
696     get _resources()
697     {
698         return this.items;
699     }
700 }
701
702 WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.AbstractTimelinePanel.prototype;
703
704 WebInspector.ResourceTimeCalculator = function(startAtZero)
705 {
706     WebInspector.AbstractTimelineCalculator.call(this);
707     this.startAtZero = startAtZero;
708 }
709
710 WebInspector.ResourceTimeCalculator.prototype = {
711     computeSummaryValues: function(resources)
712     {
713         var resourcesByCategory = {};
714         var resourcesLength = resources.length;
715         for (var i = 0; i < resourcesLength; ++i) {
716             var resource = resources[i];
717             if (!(resource.category.name in resourcesByCategory))
718                 resourcesByCategory[resource.category.name] = [];
719             resourcesByCategory[resource.category.name].push(resource);
720         }
721
722         var earliestStart;
723         var latestEnd;
724         var categoryValues = {};
725         for (var category in resourcesByCategory) {
726             resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
727             categoryValues[category] = 0;
728
729             var segment = {start: -1, end: -1};
730
731             var categoryResources = resourcesByCategory[category];
732             var resourcesLength = categoryResources.length;
733             for (var i = 0; i < resourcesLength; ++i) {
734                 var resource = categoryResources[i];
735                 if (resource.startTime === -1 || resource.endTime === -1)
736                     continue;
737
738                 if (typeof earliestStart === "undefined")
739                     earliestStart = resource.startTime;
740                 else
741                     earliestStart = Math.min(earliestStart, resource.startTime);
742
743                 if (typeof latestEnd === "undefined")
744                     latestEnd = resource.endTime;
745                 else
746                     latestEnd = Math.max(latestEnd, resource.endTime);
747
748                 if (resource.startTime <= segment.end) {
749                     segment.end = Math.max(segment.end, resource.endTime);
750                     continue;
751                 }
752
753                 categoryValues[category] += segment.end - segment.start;
754
755                 segment.start = resource.startTime;
756                 segment.end = resource.endTime;
757             }
758
759             // Add the last segment
760             categoryValues[category] += segment.end - segment.start;
761         }
762
763         return {categoryValues: categoryValues, total: latestEnd - earliestStart};
764     },
765
766     computeBarGraphPercentages: function(resource)
767     {
768         if (resource.startTime !== -1)
769             var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
770         else
771             var start = 0;
772
773         if (resource.responseReceivedTime !== -1)
774             var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
775         else
776             var middle = (this.startAtZero ? start : 100);
777
778         if (resource.endTime !== -1)
779             var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
780         else
781             var end = (this.startAtZero ? middle : 100);
782
783         if (this.startAtZero) {
784             end -= start;
785             middle -= start;
786             start = 0;
787         }
788
789         return {start: start, middle: middle, end: end};
790     },
791     
792     computePercentageFromEventTime: function(eventTime)
793     {
794         // This function computes a percentage in terms of the total loading time
795         // of a specific event. If startAtZero is set, then this is useless, and we
796         // want to return 0.
797         if (eventTime !== -1 && !this.startAtZero)
798             return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;
799
800         return 0;
801     },
802
803     computeBarGraphLabels: function(resource)
804     {
805         var leftLabel = "";
806         if (resource.latency > 0)
807             leftLabel = this.formatValue(resource.latency);
808
809         var rightLabel = "";
810         if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
811             rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
812
813         if (leftLabel && rightLabel) {
814             var total = this.formatValue(resource.duration);
815             var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
816         } else if (leftLabel)
817             var tooltip = WebInspector.UIString("%s latency", leftLabel);
818         else if (rightLabel)
819             var tooltip = WebInspector.UIString("%s download", rightLabel);
820
821         if (resource.cached)
822             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
823
824         return {left: leftLabel, right: rightLabel, tooltip: tooltip};
825     },
826
827     updateBoundaries: function(resource)
828     {
829         var didChange = false;
830
831         var lowerBound;
832         if (this.startAtZero)
833             lowerBound = 0;
834         else
835             lowerBound = this._lowerBound(resource);
836
837         if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
838             this.minimumBoundary = lowerBound;
839             didChange = true;
840         }
841
842         var upperBound = this._upperBound(resource);
843         if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
844             this.maximumBoundary = upperBound;
845             didChange = true;
846         }
847
848         return didChange;
849     },
850
851     formatValue: function(value)
852     {
853         return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
854     },
855
856     _lowerBound: function(resource)
857     {
858         return 0;
859     },
860
861     _upperBound: function(resource)
862     {
863         return 0;
864     }
865 }
866
867 WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;
868
869 WebInspector.ResourceTransferTimeCalculator = function()
870 {
871     WebInspector.ResourceTimeCalculator.call(this, false);
872 }
873
874 WebInspector.ResourceTransferTimeCalculator.prototype = {
875     formatValue: function(value)
876     {
877         return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
878     },
879
880     _lowerBound: function(resource)
881     {
882         return resource.startTime;
883     },
884
885     _upperBound: function(resource)
886     {
887         return resource.endTime;
888     }
889 }
890
891 WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
892
893 WebInspector.ResourceTransferDurationCalculator = function()
894 {
895     WebInspector.ResourceTimeCalculator.call(this, true);
896 }
897
898 WebInspector.ResourceTransferDurationCalculator.prototype = {
899     formatValue: function(value)
900     {
901         return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
902     },
903
904     _upperBound: function(resource)
905     {
906         return resource.duration;
907     }
908 }
909
910 WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
911
912 WebInspector.ResourceTransferSizeCalculator = function()
913 {
914     WebInspector.AbstractTimelineCalculator.call(this);
915 }
916
917 WebInspector.ResourceTransferSizeCalculator.prototype = {
918     computeBarGraphLabels: function(resource)
919     {
920         const label = this.formatValue(this._value(resource));
921         var tooltip = label;
922         if (resource.cached)
923             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
924         return {left: label, right: label, tooltip: tooltip};
925     },
926
927     _value: function(resource)
928     {
929         return resource.contentLength;
930     },
931
932     formatValue: function(value)
933     {
934         return Number.bytesToString(value, WebInspector.UIString.bind(WebInspector));
935     }
936 }
937
938 WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;
939
940 WebInspector.ResourceSidebarTreeElement = function(resource)
941 {
942     this.resource = resource;
943
944     this.createIconElement();
945
946     WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource);
947
948     this.refreshTitles();
949 }
950
951 WebInspector.ResourceSidebarTreeElement.prototype = {
952     onattach: function()
953     {
954         WebInspector.SidebarTreeElement.prototype.onattach.call(this);
955
956         this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
957         this._listItemNode.draggable = true;
958         
959         // FIXME: should actually add handler to parent, to be resolved via
960         // https://bugs.webkit.org/show_bug.cgi?id=30227
961         this._listItemNode.addEventListener("dragstart", this.ondragstart.bind(this), false);
962         this.updateErrorsAndWarnings();
963     },
964
965     onselect: function()
966     {
967         WebInspector.panels.resources.showResource(this.resource);
968     },
969     
970     ondblclick: function(treeElement, event)
971     {
972         InjectedScriptAccess.openInInspectedWindow(this.resource.url, function() {});
973     },
974
975     ondragstart: function(event) {
976         event.dataTransfer.setData("text/plain", this.resource.url);
977         event.dataTransfer.setData("text/uri-list", this.resource.url + "\r\n");
978         event.dataTransfer.effectAllowed = "copy";
979         return true;
980     },
981
982     get mainTitle()
983     {
984         return this.resource.displayName;
985     },
986
987     set mainTitle(x)
988     {
989         // Do nothing.
990     },
991
992     get subtitle()
993     {
994         var subtitle = this.resource.displayDomain;
995
996         if (this.resource.path && this.resource.lastPathComponent) {
997             var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent);
998             if (lastPathComponentIndex != -1)
999                 subtitle += this.resource.path.substring(0, lastPathComponentIndex);
1000         }
1001
1002         return subtitle;
1003     },
1004
1005     set subtitle(x)
1006     {
1007         // Do nothing.
1008     },
1009
1010     get selectable()
1011     {
1012         return WebInspector.panels.resources.isCategoryVisible(this.resource.category.name);
1013     },
1014
1015     createIconElement: function()
1016     {
1017         var previousIconElement = this.iconElement;
1018
1019         if (this.resource.category === WebInspector.resourceCategories.images) {
1020             var previewImage = document.createElement("img");
1021             previewImage.className = "image-resource-icon-preview";
1022             previewImage.src = this.resource.url;
1023
1024             this.iconElement = document.createElement("div");
1025             this.iconElement.className = "icon";
1026             this.iconElement.appendChild(previewImage);
1027         } else {
1028             this.iconElement = document.createElement("img");
1029             this.iconElement.className = "icon";
1030         }
1031
1032         if (previousIconElement)
1033             previousIconElement.parentNode.replaceChild(this.iconElement, previousIconElement);
1034     },
1035
1036     refresh: function()
1037     {
1038         this.refreshTitles();
1039
1040         if (!this._listItemNode.hasStyleClass("resources-category-" + this.resource.category.name)) {
1041             this._listItemNode.removeMatchingStyleClasses("resources-category-\\w+");
1042             this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
1043
1044             this.createIconElement();
1045         }
1046     },
1047
1048     resetBubble: function()
1049     {
1050         this.bubbleText = "";
1051         this.bubbleElement.removeStyleClass("search-matches");
1052         this.bubbleElement.removeStyleClass("warning");
1053         this.bubbleElement.removeStyleClass("error");
1054     },
1055
1056     set searchMatches(matches)
1057     {
1058         this.resetBubble();
1059
1060         if (!matches)
1061             return;
1062
1063         this.bubbleText = matches;
1064         this.bubbleElement.addStyleClass("search-matches");
1065     },
1066
1067     updateErrorsAndWarnings: function()
1068     {
1069         this.resetBubble();
1070
1071         if (this.resource.warnings || this.resource.errors)
1072             this.bubbleText = (this.resource.warnings + this.resource.errors);
1073
1074         if (this.resource.warnings)
1075             this.bubbleElement.addStyleClass("warning");
1076
1077         if (this.resource.errors)
1078             this.bubbleElement.addStyleClass("error");
1079     }
1080 }
1081
1082 WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b)
1083 {
1084     return WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1085         || WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
1086         || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
1087 }
1088
1089 WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b)
1090 {
1091     return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource)
1092         || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1093         || WebInspector.Resource.CompareByEndTime(a.resource, b.resource);
1094 }
1095
1096 WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b)
1097 {
1098     return WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
1099         || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1100         || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
1101 }
1102
1103 WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b)
1104 {
1105     return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource);
1106 }
1107
1108 WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b)
1109 {
1110     return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource);
1111 }
1112
1113 WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b)
1114 {
1115     return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource);
1116 }
1117
1118 WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
1119
1120 WebInspector.ResourceGraph = function(resource)
1121 {
1122     this.resource = resource;
1123
1124     this._graphElement = document.createElement("div");
1125     this._graphElement.className = "resources-graph-side";
1126     this._graphElement.addEventListener("mouseover", this.refreshLabelPositions.bind(this), false);
1127
1128     if (resource.cached)
1129         this._graphElement.addStyleClass("resource-cached");
1130
1131     this._barAreaElement = document.createElement("div");
1132     this._barAreaElement.className = "resources-graph-bar-area hidden";
1133     this._graphElement.appendChild(this._barAreaElement);
1134
1135     this._barLeftElement = document.createElement("div");
1136     this._barLeftElement.className = "resources-graph-bar waiting";
1137     this._barAreaElement.appendChild(this._barLeftElement);
1138
1139     this._barRightElement = document.createElement("div");
1140     this._barRightElement.className = "resources-graph-bar";
1141     this._barAreaElement.appendChild(this._barRightElement);
1142
1143     this._labelLeftElement = document.createElement("div");
1144     this._labelLeftElement.className = "resources-graph-label waiting";
1145     this._barAreaElement.appendChild(this._labelLeftElement);
1146
1147     this._labelRightElement = document.createElement("div");
1148     this._labelRightElement.className = "resources-graph-label";
1149     this._barAreaElement.appendChild(this._labelRightElement);
1150
1151     this._graphElement.addStyleClass("resources-category-" + resource.category.name);
1152 }
1153
1154 WebInspector.ResourceGraph.prototype = {
1155     get graphElement()
1156     {
1157         return this._graphElement;
1158     },
1159
1160     refreshLabelPositions: function()
1161     {
1162         this._labelLeftElement.style.removeProperty("left");
1163         this._labelLeftElement.style.removeProperty("right");
1164         this._labelLeftElement.removeStyleClass("before");
1165         this._labelLeftElement.removeStyleClass("hidden");
1166
1167         this._labelRightElement.style.removeProperty("left");
1168         this._labelRightElement.style.removeProperty("right");
1169         this._labelRightElement.removeStyleClass("after");
1170         this._labelRightElement.removeStyleClass("hidden");
1171
1172         const labelPadding = 10;
1173         const rightBarWidth = (this._barRightElement.offsetWidth - labelPadding);
1174         const leftBarWidth = ((this._barLeftElement.offsetWidth - this._barRightElement.offsetWidth) - labelPadding);
1175
1176         var labelBefore = (this._labelLeftElement.offsetWidth > leftBarWidth);
1177         var labelAfter = (this._labelRightElement.offsetWidth > rightBarWidth);
1178
1179         if (labelBefore) {
1180             if ((this._graphElement.offsetWidth * (this._percentages.start / 100)) < (this._labelLeftElement.offsetWidth + 10))
1181                 this._labelLeftElement.addStyleClass("hidden");
1182             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
1183             this._labelLeftElement.addStyleClass("before");
1184         } else {
1185             this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
1186             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
1187         }
1188
1189         if (labelAfter) {
1190             if ((this._graphElement.offsetWidth * ((100 - this._percentages.end) / 100)) < (this._labelRightElement.offsetWidth + 10))
1191                 this._labelRightElement.addStyleClass("hidden");
1192             this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
1193             this._labelRightElement.addStyleClass("after");
1194         } else {
1195             this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
1196             this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
1197         }
1198     },
1199
1200     refresh: function(calculator)
1201     {
1202         var percentages = calculator.computeBarGraphPercentages(this.resource);
1203         var labels = calculator.computeBarGraphLabels(this.resource);
1204
1205         this._percentages = percentages;
1206
1207         this._barAreaElement.removeStyleClass("hidden");
1208
1209         if (!this._graphElement.hasStyleClass("resources-category-" + this.resource.category.name)) {
1210             this._graphElement.removeMatchingStyleClasses("resources-category-\\w+");
1211             this._graphElement.addStyleClass("resources-category-" + this.resource.category.name);
1212         }
1213
1214         this._barLeftElement.style.setProperty("left", percentages.start + "%");
1215         this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
1216
1217         this._barRightElement.style.setProperty("left", percentages.middle + "%");
1218         this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
1219
1220         this._labelLeftElement.textContent = labels.left;
1221         this._labelRightElement.textContent = labels.right;
1222
1223         var tooltip = (labels.tooltip || "");
1224         this._barLeftElement.title = tooltip;
1225         this._labelLeftElement.title = tooltip;
1226         this._labelRightElement.title = tooltip;
1227         this._barRightElement.title = tooltip;
1228     }
1229 }