• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
30WebInspector.ResourcesPanel = function()
31{
32    WebInspector.Panel.call(this);
33
34    this.element.addStyleClass("resources");
35
36    this.filterBarElement = document.createElement("div");
37    this.filterBarElement.id = "resources-filter";
38    this.element.appendChild(this.filterBarElement);
39
40    this.viewsContainerElement = document.createElement("div");
41    this.viewsContainerElement.id = "resource-views";
42    this.element.appendChild(this.viewsContainerElement);
43
44    this.containerElement = document.createElement("div");
45    this.containerElement.id = "resources-container";
46    this.containerElement.addEventListener("scroll", this._updateDividersLabelBarPosition.bind(this), false);
47    this.element.appendChild(this.containerElement);
48
49    this.sidebarElement = document.createElement("div");
50    this.sidebarElement.id = "resources-sidebar";
51    this.sidebarElement.className = "sidebar";
52    this.containerElement.appendChild(this.sidebarElement);
53
54    this.sidebarResizeElement = document.createElement("div");
55    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
56    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false);
57    this.element.appendChild(this.sidebarResizeElement);
58
59    this.containerContentElement = document.createElement("div");
60    this.containerContentElement.id = "resources-container-content";
61    this.containerElement.appendChild(this.containerContentElement);
62
63    this.summaryElement = document.createElement("div");
64    this.summaryElement.id = "resources-summary";
65    this.containerContentElement.appendChild(this.summaryElement);
66
67    this.resourcesGraphsElement = document.createElement("div");
68    this.resourcesGraphsElement.id = "resources-graphs";
69    this.containerContentElement.appendChild(this.resourcesGraphsElement);
70
71    this.dividersElement = document.createElement("div");
72    this.dividersElement.id = "resources-dividers";
73    this.containerContentElement.appendChild(this.dividersElement);
74
75    this.dividersLabelBarElement = document.createElement("div");
76    this.dividersLabelBarElement.id = "resources-dividers-label-bar";
77    this.containerContentElement.appendChild(this.dividersLabelBarElement);
78
79    this.summaryGraphElement = document.createElement("canvas");
80    this.summaryGraphElement.setAttribute("width", "450");
81    this.summaryGraphElement.setAttribute("height", "38");
82    this.summaryGraphElement.id = "resources-summary-graph";
83    this.summaryElement.appendChild(this.summaryGraphElement);
84
85    this.legendElement = document.createElement("div");
86    this.legendElement.id = "resources-graph-legend";
87    this.summaryElement.appendChild(this.legendElement);
88
89    this.sidebarTreeElement = document.createElement("ol");
90    this.sidebarTreeElement.className = "sidebar-tree";
91    this.sidebarElement.appendChild(this.sidebarTreeElement);
92
93    this.sidebarTree = new TreeOutline(this.sidebarTreeElement);
94
95    var timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time"));
96    timeGraphItem.onselect = this._graphSelected.bind(this);
97
98    var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator();
99    var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator();
100
101    timeGraphItem.sortingOptions = [
102        { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator },
103        { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator },
104        { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator },
105        { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator },
106        { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator },
107    ];
108
109    timeGraphItem.selectedSortingOptionIndex = 1;
110
111    var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size"));
112    sizeGraphItem.onselect = this._graphSelected.bind(this);
113
114    var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator();
115    sizeGraphItem.sortingOptions = [
116        { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator },
117    ];
118
119    sizeGraphItem.selectedSortingOptionIndex = 0;
120
121    this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true);
122    this.sidebarTree.appendChild(this.graphsTreeElement);
123
124    this.graphsTreeElement.appendChild(timeGraphItem);
125    this.graphsTreeElement.appendChild(sizeGraphItem);
126    this.graphsTreeElement.expand();
127
128    this.resourcesTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true);
129    this.sidebarTree.appendChild(this.resourcesTreeElement);
130
131    this.resourcesTreeElement.expand();
132
133    var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel.");
134    var panelEnablerDisclaimer = WebInspector.UIString("Enabling resource tracking will reload the page and make page loading slower.");
135    var panelEnablerButton = WebInspector.UIString("Enable resource tracking");
136
137    this.panelEnablerView = new WebInspector.PanelEnablerView("resources", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
138    this.panelEnablerView.addEventListener("enable clicked", this._enableResourceTracking, this);
139
140    this.element.appendChild(this.panelEnablerView.element);
141
142    this.enableToggleButton = this.createStatusBarButton();
143    this.enableToggleButton.className = "enable-toggle-status-bar-item status-bar-item";
144    this.enableToggleButton.addEventListener("click", this._toggleResourceTracking.bind(this), false);
145
146    this.largerResourcesButton = this.createStatusBarButton();
147    this.largerResourcesButton.id = "resources-larger-resources-status-bar-item";
148    this.largerResourcesButton.className = "status-bar-item toggled-on";
149    this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
150    this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
151
152    this.sortingSelectElement = document.createElement("select");
153    this.sortingSelectElement.className = "status-bar-item";
154    this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false);
155
156    var createFilterElement = function (category) {
157        var categoryElement = document.createElement("li");
158        categoryElement.category = category;
159        categoryElement.addStyleClass(category);
160        var label = WebInspector.UIString("All");
161        if (WebInspector.resourceCategories[category])
162            label = WebInspector.resourceCategories[category].title;
163        categoryElement.appendChild(document.createTextNode(label));
164        categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
165        this.filterBarElement.appendChild(categoryElement);
166        return categoryElement;
167    };
168
169    var allElement = createFilterElement.call(this, "all");
170    this.filter(allElement.category);
171    for (var i = 0; i < this.categoryOrder.length; i++)
172        createFilterElement.call(this, this.categoryOrder[i]);
173
174    this.reset();
175
176    timeGraphItem.select();
177}
178
179WebInspector.ResourcesPanel.prototype = {
180    toolbarItemClass: "resources",
181
182    categoryOrder: ["documents", "stylesheets", "images", "scripts", "xhr", "fonts", "other"],
183
184    filter: function (category) {
185        if (this._filterCategory && this._filterCategory === category)
186            return;
187
188        if (this._filterCategory) {
189            var filterElement = this.filterBarElement.getElementsByClassName(this._filterCategory)[0];
190            filterElement.removeStyleClass("selected");
191            var oldClass = "filter-" + this._filterCategory;
192            this.resourcesTreeElement.childrenListElement.removeStyleClass(oldClass);
193            this.resourcesGraphsElement.removeStyleClass(oldClass);
194        }
195        this._filterCategory = category;
196        var filterElement = this.filterBarElement.getElementsByClassName(this._filterCategory)[0];
197        filterElement.addStyleClass("selected");
198        var newClass = "filter-" + this._filterCategory;
199        this.resourcesTreeElement.childrenListElement.addStyleClass(newClass);
200        this.resourcesGraphsElement.addStyleClass(newClass);
201    },
202
203    _updateFilter: function (e) {
204        this.filter(e.target.category);
205    },
206
207    get toolbarItemLabel()
208    {
209        return WebInspector.UIString("Resources");
210    },
211
212    get statusBarItems()
213    {
214        return [this.enableToggleButton, this.largerResourcesButton, this.sortingSelectElement];
215    },
216
217    show: function()
218    {
219        WebInspector.Panel.prototype.show.call(this);
220
221        this._updateDividersLabelBarPosition();
222        this._updateSidebarWidth();
223        this.refreshIfNeeded();
224
225        var visibleView = this.visibleView;
226        if (visibleView) {
227            visibleView.headersVisible = true;
228            visibleView.show(this.viewsContainerElement);
229        }
230
231        // Hide any views that are visible that are not this panel's current visible view.
232        // This can happen when a ResourceView is visible in the Scripts panel then switched
233        // to the this panel.
234        var resourcesLength = this._resources.length;
235        for (var i = 0; i < resourcesLength; ++i) {
236            var resource = this._resources[i];
237            var view = resource._resourcesView;
238            if (!view || view === visibleView)
239                continue;
240            view.visible = false;
241        }
242    },
243
244    resize: function()
245    {
246        this._updateGraphDividersIfNeeded();
247
248        var visibleView = this.visibleView;
249        if (visibleView && "resize" in visibleView)
250            visibleView.resize();
251    },
252
253    get searchableViews()
254    {
255        var views = [];
256
257        const visibleView = this.visibleView;
258        if (visibleView && visibleView.performSearch)
259            views.push(visibleView);
260
261        var resourcesLength = this._resources.length;
262        for (var i = 0; i < resourcesLength; ++i) {
263            var resource = this._resources[i];
264            if (!resource._resourcesTreeElement)
265                continue;
266            var resourceView = this.resourceViewForResource(resource);
267            if (!resourceView.performSearch || resourceView === visibleView)
268                continue;
269            views.push(resourceView);
270        }
271
272        return views;
273    },
274
275    get searchResultsSortFunction()
276    {
277        const resourceTreeElementSortFunction = this.sortingFunction;
278
279        function sortFuction(a, b)
280        {
281            return resourceTreeElementSortFunction(a.resource._resourcesTreeElement, b.resource._resourcesTreeElement);
282        }
283
284        return sortFuction;
285    },
286
287    searchMatchFound: function(view, matches)
288    {
289        view.resource._resourcesTreeElement.searchMatches = matches;
290    },
291
292    searchCanceled: function(startingNewSearch)
293    {
294        WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
295
296        if (startingNewSearch || !this._resources)
297            return;
298
299        for (var i = 0; i < this._resources.length; ++i) {
300            var resource = this._resources[i];
301            if (resource._resourcesTreeElement)
302                resource._resourcesTreeElement.updateErrorsAndWarnings();
303        }
304    },
305
306    performSearch: function(query)
307    {
308        for (var i = 0; i < this._resources.length; ++i) {
309            var resource = this._resources[i];
310            if (resource._resourcesTreeElement)
311                resource._resourcesTreeElement.resetBubble();
312        }
313
314        WebInspector.Panel.prototype.performSearch.call(this, query);
315    },
316
317    get visibleView()
318    {
319        if (this.visibleResource)
320            return this.visibleResource._resourcesView;
321        return null;
322    },
323
324    get calculator()
325    {
326        return this._calculator;
327    },
328
329    set calculator(x)
330    {
331        if (!x || this._calculator === x)
332            return;
333
334        this._calculator = x;
335        this._calculator.reset();
336
337        this._staleResources = this._resources;
338        this.refresh();
339    },
340
341    get sortingFunction()
342    {
343        return this._sortingFunction;
344    },
345
346    set sortingFunction(x)
347    {
348        this._sortingFunction = x;
349        this._sortResourcesIfNeeded();
350    },
351
352    get needsRefresh()
353    {
354        return this._needsRefresh;
355    },
356
357    set needsRefresh(x)
358    {
359        if (this._needsRefresh === x)
360            return;
361
362        this._needsRefresh = x;
363
364        if (x) {
365            if (this.visible && !("_refreshTimeout" in this))
366                this._refreshTimeout = setTimeout(this.refresh.bind(this), 500);
367        } else {
368            if ("_refreshTimeout" in this) {
369                clearTimeout(this._refreshTimeout);
370                delete this._refreshTimeout;
371            }
372        }
373    },
374
375    refreshIfNeeded: function()
376    {
377        if (this.needsRefresh)
378            this.refresh();
379    },
380
381    refresh: function()
382    {
383        this.needsRefresh = false;
384
385        var staleResourcesLength = this._staleResources.length;
386        var boundariesChanged = false;
387
388        for (var i = 0; i < staleResourcesLength; ++i) {
389            var resource = this._staleResources[i];
390            if (!resource._resourcesTreeElement) {
391                // Create the resource tree element and graph.
392                resource._resourcesTreeElement = new WebInspector.ResourceSidebarTreeElement(resource);
393                resource._resourcesTreeElement._resourceGraph = new WebInspector.ResourceGraph(resource);
394
395                this.resourcesTreeElement.appendChild(resource._resourcesTreeElement);
396                this.resourcesGraphsElement.appendChild(resource._resourcesTreeElement._resourceGraph.graphElement);
397            }
398
399            resource._resourcesTreeElement.refresh();
400
401            if (this.calculator.updateBoundaries(resource))
402                boundariesChanged = true;
403        }
404
405        if (boundariesChanged) {
406            // The boundaries changed, so all resource graphs are stale.
407            this._staleResources = this._resources;
408            staleResourcesLength = this._staleResources.length;
409        }
410
411        for (var i = 0; i < staleResourcesLength; ++i)
412            this._staleResources[i]._resourcesTreeElement._resourceGraph.refresh(this.calculator);
413
414        this._staleResources = [];
415
416        this._updateGraphDividersIfNeeded();
417        this._sortResourcesIfNeeded();
418        this._updateSummaryGraph();
419    },
420
421    resourceTrackingWasEnabled: function()
422    {
423        this.reset();
424    },
425
426    resourceTrackingWasDisabled: function()
427    {
428        this.reset();
429    },
430
431    reset: function()
432    {
433        this.closeVisibleResource();
434
435        this.containerElement.scrollTop = 0;
436
437        delete this.currentQuery;
438        this.searchCanceled();
439
440        if (this._calculator)
441            this._calculator.reset();
442
443        if (this._resources) {
444            var resourcesLength = this._resources.length;
445            for (var i = 0; i < resourcesLength; ++i) {
446                var resource = this._resources[i];
447
448                resource.warnings = 0;
449                resource.errors = 0;
450
451                delete resource._resourcesTreeElement;
452                delete resource._resourcesView;
453            }
454        }
455
456        this._resources = [];
457        this._staleResources = [];
458
459        this.resourcesTreeElement.removeChildren();
460        this.viewsContainerElement.removeChildren();
461        this.resourcesGraphsElement.removeChildren();
462        this.legendElement.removeChildren();
463
464        this._updateGraphDividersIfNeeded(true);
465
466        this._drawSummaryGraph(); // draws an empty graph
467
468        if (InspectorController.resourceTrackingEnabled()) {
469            this.enableToggleButton.title = WebInspector.UIString("Resource tracking enabled. Click to disable.");
470            this.enableToggleButton.addStyleClass("toggled-on");
471            this.largerResourcesButton.removeStyleClass("hidden");
472            this.sortingSelectElement.removeStyleClass("hidden");
473            this.panelEnablerView.visible = false;
474        } else {
475            this.enableToggleButton.title = WebInspector.UIString("Resource tracking disabled. Click to enable.");
476            this.enableToggleButton.removeStyleClass("toggled-on");
477            this.largerResourcesButton.addStyleClass("hidden");
478            this.sortingSelectElement.addStyleClass("hidden");
479            this.panelEnablerView.visible = true;
480        }
481    },
482
483    addResource: function(resource)
484    {
485        this._resources.push(resource);
486        this.refreshResource(resource);
487    },
488
489    removeResource: function(resource)
490    {
491        if (this.visibleView === resource._resourcesView)
492            this.closeVisibleResource();
493
494        this._resources.remove(resource, true);
495
496        if (resource._resourcesTreeElement) {
497            this.resourcesTreeElement.removeChild(resource._resourcesTreeElement);
498            this.resourcesGraphsElement.removeChild(resource._resourcesTreeElement._resourceGraph.graphElement);
499        }
500
501        resource.warnings = 0;
502        resource.errors = 0;
503
504        delete resource._resourcesTreeElement;
505        delete resource._resourcesView;
506
507        this._adjustScrollPosition();
508    },
509
510    addMessageToResource: function(resource, msg)
511    {
512        if (!resource)
513            return;
514
515        switch (msg.level) {
516        case WebInspector.ConsoleMessage.MessageLevel.Warning:
517            resource.warnings += msg.repeatDelta;
518            break;
519        case WebInspector.ConsoleMessage.MessageLevel.Error:
520            resource.errors += msg.repeatDelta;
521            break;
522        }
523
524        if (!this.currentQuery && resource._resourcesTreeElement)
525            resource._resourcesTreeElement.updateErrorsAndWarnings();
526
527        var view = this.resourceViewForResource(resource);
528        if (view.addMessage)
529            view.addMessage(msg);
530    },
531
532    clearMessages: function()
533    {
534        var resourcesLength = this._resources.length;
535        for (var i = 0; i < resourcesLength; ++i) {
536            var resource = this._resources[i];
537            resource.warnings = 0;
538            resource.errors = 0;
539
540            if (!this.currentQuery && resource._resourcesTreeElement)
541                resource._resourcesTreeElement.updateErrorsAndWarnings();
542
543            var view = resource._resourcesView;
544            if (!view || !view.clearMessages)
545                continue;
546            view.clearMessages();
547        }
548    },
549
550    refreshResource: function(resource)
551    {
552        this._staleResources.push(resource);
553        this.needsRefresh = true;
554    },
555
556    recreateViewForResourceIfNeeded: function(resource)
557    {
558        if (!resource || !resource._resourcesView)
559            return;
560
561        var newView = this._createResourceView(resource);
562        if (newView.prototype === resource._resourcesView.prototype)
563            return;
564
565        resource.warnings = 0;
566        resource.errors = 0;
567
568        if (!this.currentQuery && resource._resourcesTreeElement)
569            resource._resourcesTreeElement.updateErrorsAndWarnings();
570
571        var oldView = resource._resourcesView;
572
573        resource._resourcesView.detach();
574        delete resource._resourcesView;
575
576        resource._resourcesView = newView;
577
578        newView.headersVisible = oldView.headersVisible;
579
580        if (oldView.visible && oldView.element.parentNode)
581            newView.show(oldView.element.parentNode);
582    },
583
584    showResource: function(resource, line)
585    {
586        if (!resource)
587            return;
588
589        this.containerElement.addStyleClass("viewing-resource");
590
591        if (this.visibleResource && this.visibleResource._resourcesView)
592            this.visibleResource._resourcesView.hide();
593
594        var view = this.resourceViewForResource(resource);
595        view.headersVisible = true;
596        view.show(this.viewsContainerElement);
597
598        if (line) {
599            if (view.revealLine)
600                view.revealLine(line);
601            if (view.highlightLine)
602                view.highlightLine(line);
603        }
604
605        if (resource._resourcesTreeElement) {
606            resource._resourcesTreeElement.reveal();
607            resource._resourcesTreeElement.select(true);
608        }
609
610        this.visibleResource = resource;
611
612        this._updateSidebarWidth();
613    },
614
615    showView: function(view)
616    {
617        if (!view)
618            return;
619        this.showResource(view.resource);
620    },
621
622    closeVisibleResource: function()
623    {
624        this.containerElement.removeStyleClass("viewing-resource");
625        this._updateDividersLabelBarPosition();
626
627        if (this.visibleResource && this.visibleResource._resourcesView)
628            this.visibleResource._resourcesView.hide();
629        delete this.visibleResource;
630
631        if (this._lastSelectedGraphTreeElement)
632            this._lastSelectedGraphTreeElement.select(true);
633
634        this._updateSidebarWidth();
635    },
636
637    resourceViewForResource: function(resource)
638    {
639        if (!resource)
640            return null;
641        if (!resource._resourcesView)
642            resource._resourcesView = this._createResourceView(resource);
643        return resource._resourcesView;
644    },
645
646    sourceFrameForResource: function(resource)
647    {
648        var view = this.resourceViewForResource(resource);
649        if (!view)
650            return null;
651
652        if (!view.setupSourceFrameIfNeeded)
653            return null;
654
655        // Setting up the source frame requires that we be attached.
656        if (!this.element.parentNode)
657            this.attach();
658
659        view.setupSourceFrameIfNeeded();
660        return view.sourceFrame;
661    },
662
663    handleKeyEvent: function(event)
664    {
665        this.sidebarTree.handleKeyEvent(event);
666    },
667
668    _makeLegendElement: function(label, value, color)
669    {
670        var legendElement = document.createElement("label");
671        legendElement.className = "resources-graph-legend-item";
672
673        if (color) {
674            var swatch = document.createElement("canvas");
675            swatch.className = "resources-graph-legend-swatch";
676            swatch.setAttribute("width", "13");
677            swatch.setAttribute("height", "24");
678
679            legendElement.appendChild(swatch);
680
681            this._drawSwatch(swatch, color);
682        }
683
684        var labelElement = document.createElement("div");
685        labelElement.className = "resources-graph-legend-label";
686        legendElement.appendChild(labelElement);
687
688        var headerElement = document.createElement("div");
689        var headerElement = document.createElement("div");
690        headerElement.className = "resources-graph-legend-header";
691        headerElement.textContent = label;
692        labelElement.appendChild(headerElement);
693
694        var valueElement = document.createElement("div");
695        valueElement.className = "resources-graph-legend-value";
696        valueElement.textContent = value;
697        labelElement.appendChild(valueElement);
698
699        return legendElement;
700    },
701
702    _sortResourcesIfNeeded: function()
703    {
704        var sortedElements = [].concat(this.resourcesTreeElement.children);
705        sortedElements.sort(this.sortingFunction);
706
707        var sortedElementsLength = sortedElements.length;
708        for (var i = 0; i < sortedElementsLength; ++i) {
709            var treeElement = sortedElements[i];
710            if (treeElement === this.resourcesTreeElement.children[i])
711                continue;
712
713            var wasSelected = treeElement.selected;
714            this.resourcesTreeElement.removeChild(treeElement);
715            this.resourcesTreeElement.insertChild(treeElement, i);
716            if (wasSelected)
717                treeElement.select(true);
718
719            var graphElement = treeElement._resourceGraph.graphElement;
720            this.resourcesGraphsElement.insertBefore(graphElement, this.resourcesGraphsElement.children[i]);
721        }
722    },
723
724    _updateGraphDividersIfNeeded: function(force)
725    {
726        if (!this.visible) {
727            this.needsRefresh = true;
728            return;
729        }
730
731        if (document.body.offsetWidth <= 0) {
732            // The stylesheet hasn't loaded yet or the window is closed,
733            // so we can't calculate what is need. Return early.
734            return;
735        }
736
737        var dividerCount = Math.round(this.dividersElement.offsetWidth / 64);
738        var slice = this.calculator.boundarySpan / dividerCount;
739        if (!force && this._currentDividerSlice === slice)
740            return;
741
742        this._currentDividerSlice = slice;
743
744        this.dividersElement.removeChildren();
745        this.dividersLabelBarElement.removeChildren();
746
747        for (var i = 1; i <= dividerCount; ++i) {
748            var divider = document.createElement("div");
749            divider.className = "resources-divider";
750            if (i === dividerCount)
751                divider.addStyleClass("last");
752            divider.style.left = ((i / dividerCount) * 100) + "%";
753
754            this.dividersElement.appendChild(divider.cloneNode());
755
756            var label = document.createElement("div");
757            label.className = "resources-divider-label";
758            if (!isNaN(slice))
759                label.textContent = this.calculator.formatValue(slice * i);
760            divider.appendChild(label);
761
762            this.dividersLabelBarElement.appendChild(divider);
763        }
764    },
765
766    _fadeOutRect: function(ctx, x, y, w, h, a1, a2)
767    {
768        ctx.save();
769
770        var gradient = ctx.createLinearGradient(x, y, x, y + h);
771        gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")");
772        gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")");
773        gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)");
774
775        ctx.globalCompositeOperation = "destination-out";
776
777        ctx.fillStyle = gradient;
778        ctx.fillRect(x, y, w, h);
779
780        ctx.restore();
781    },
782
783    _drawSwatch: function(canvas, color)
784    {
785        var ctx = canvas.getContext("2d");
786
787        function drawSwatchSquare() {
788            ctx.fillStyle = color;
789            ctx.fillRect(0, 0, 13, 13);
790
791            var gradient = ctx.createLinearGradient(0, 0, 13, 13);
792            gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)");
793            gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");
794
795            ctx.fillStyle = gradient;
796            ctx.fillRect(0, 0, 13, 13);
797
798            gradient = ctx.createLinearGradient(13, 13, 0, 0);
799            gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)");
800            gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)");
801
802            ctx.fillStyle = gradient;
803            ctx.fillRect(0, 0, 13, 13);
804
805            ctx.strokeStyle = "rgba(0, 0, 0, 0.6)";
806            ctx.strokeRect(0.5, 0.5, 12, 12);
807        }
808
809        ctx.clearRect(0, 0, 13, 24);
810
811        drawSwatchSquare();
812
813        ctx.save();
814
815        ctx.translate(0, 25);
816        ctx.scale(1, -1);
817
818        drawSwatchSquare();
819
820        ctx.restore();
821
822        this._fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0);
823    },
824
825    _drawSummaryGraph: function(segments)
826    {
827        if (!this.summaryGraphElement)
828            return;
829
830        if (!segments || !segments.length) {
831            segments = [{color: "white", value: 1}];
832            this._showingEmptySummaryGraph = true;
833        } else
834            delete this._showingEmptySummaryGraph;
835
836        // Calculate the total of all segments.
837        var total = 0;
838        for (var i = 0; i < segments.length; ++i)
839            total += segments[i].value;
840
841        // Calculate the percentage of each segment, rounded to the nearest percent.
842        var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) });
843
844        // Calculate the total percentage.
845        var percentTotal = 0;
846        for (var i = 0; i < percents.length; ++i)
847            percentTotal += percents[i];
848
849        // Make sure our percentage total is not greater-than 100, it can be greater
850        // if we rounded up for a few segments.
851        while (percentTotal > 100) {
852            for (var i = 0; i < percents.length && percentTotal > 100; ++i) {
853                if (percents[i] > 1) {
854                    --percents[i];
855                    --percentTotal;
856                }
857            }
858        }
859
860        // Make sure our percentage total is not less-than 100, it can be less
861        // if we rounded down for a few segments.
862        while (percentTotal < 100) {
863            for (var i = 0; i < percents.length && percentTotal < 100; ++i) {
864                ++percents[i];
865                ++percentTotal;
866            }
867        }
868
869        var ctx = this.summaryGraphElement.getContext("2d");
870
871        var x = 0;
872        var y = 0;
873        var w = 450;
874        var h = 19;
875        var r = (h / 2);
876
877        function drawPillShadow()
878        {
879            // This draws a line with a shadow that is offset away from the line. The line is stroked
880            // twice with different X shadow offsets to give more feathered edges. Later we erase the
881            // line with destination-out 100% transparent black, leaving only the shadow. This only
882            // works if nothing has been drawn into the canvas yet.
883
884            ctx.beginPath();
885            ctx.moveTo(x + 4, y + h - 3 - 0.5);
886            ctx.lineTo(x + w - 4, y + h - 3 - 0.5);
887            ctx.closePath();
888
889            ctx.save();
890
891            ctx.shadowBlur = 2;
892            ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
893            ctx.shadowOffsetX = 3;
894            ctx.shadowOffsetY = 5;
895
896            ctx.strokeStyle = "white";
897            ctx.lineWidth = 1;
898
899            ctx.stroke();
900
901            ctx.shadowOffsetX = -3;
902
903            ctx.stroke();
904
905            ctx.restore();
906
907            ctx.save();
908
909            ctx.globalCompositeOperation = "destination-out";
910            ctx.strokeStyle = "rgba(0, 0, 0, 1)";
911            ctx.lineWidth = 1;
912
913            ctx.stroke();
914
915            ctx.restore();
916        }
917
918        function drawPill()
919        {
920            // Make a rounded rect path.
921            ctx.beginPath();
922            ctx.moveTo(x, y + r);
923            ctx.lineTo(x, y + h - r);
924            ctx.quadraticCurveTo(x, y + h, x + r, y + h);
925            ctx.lineTo(x + w - r, y + h);
926            ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - r);
927            ctx.lineTo(x + w, y + r);
928            ctx.quadraticCurveTo(x + w, y, x + w - r, y);
929            ctx.lineTo(x + r, y);
930            ctx.quadraticCurveTo(x, y, x, y + r);
931            ctx.closePath();
932
933            // Clip to the rounded rect path.
934            ctx.save();
935            ctx.clip();
936
937            // Fill the segments with the associated color.
938            var previousSegmentsWidth = 0;
939            for (var i = 0; i < segments.length; ++i) {
940                var segmentWidth = Math.round(w * percents[i] / 100);
941                ctx.fillStyle = segments[i].color;
942                ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h);
943                previousSegmentsWidth += segmentWidth;
944            }
945
946            // Draw the segment divider lines.
947            ctx.lineWidth = 1;
948            for (var i = 1; i < 20; ++i) {
949                ctx.beginPath();
950                ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y);
951                ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h);
952                ctx.closePath();
953
954                ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
955                ctx.stroke();
956
957                ctx.beginPath();
958                ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y);
959                ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h);
960                ctx.closePath();
961
962                ctx.strokeStyle = "rgba(255, 255, 255, 0.2)";
963                ctx.stroke();
964            }
965
966            // Draw the pill shading.
967            var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5));
968            lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)");
969            lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)");
970            lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)");
971
972            var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h);
973            darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)");
974            darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)");
975            darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)");
976
977            ctx.fillStyle = darkGradient;
978            ctx.fillRect(x, y, w, h);
979
980            ctx.fillStyle = lightGradient;
981            ctx.fillRect(x, y, w, h);
982
983            ctx.restore();
984        }
985
986        ctx.clearRect(x, y, w, (h * 2));
987
988        drawPillShadow();
989        drawPill();
990
991        ctx.save();
992
993        ctx.translate(0, (h * 2) + 1);
994        ctx.scale(1, -1);
995
996        drawPill();
997
998        ctx.restore();
999
1000        this._fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0);
1001    },
1002
1003    _updateSummaryGraph: function()
1004    {
1005        var graphInfo = this.calculator.computeSummaryValues(this._resources);
1006
1007        var categoryColors = {documents: {r: 47, g: 102, b: 236}, stylesheets: {r: 157, g: 231, b: 119}, images: {r: 164, g: 60, b: 255}, scripts: {r: 255, g: 121, b: 0}, xhr: {r: 231, g: 231, b: 10}, fonts: {r: 255, g: 82, b: 62}, other: {r: 186, g: 186, b: 186}};
1008        var fillSegments = [];
1009
1010        this.legendElement.removeChildren();
1011
1012        for (var i = 0; i < this.categoryOrder.length; ++i) {
1013            var category = this.categoryOrder[i];
1014            var size = graphInfo.categoryValues[category];
1015            if (!size)
1016                continue;
1017
1018            var color = categoryColors[category];
1019            var colorString = "rgb(" + color.r + ", " + color.g + ", " + color.b + ")";
1020
1021            var fillSegment = {color: colorString, value: size};
1022            fillSegments.push(fillSegment);
1023
1024            var legendLabel = this._makeLegendElement(WebInspector.resourceCategories[category].title, this.calculator.formatValue(size), colorString);
1025            this.legendElement.appendChild(legendLabel);
1026        }
1027
1028        if (graphInfo.total) {
1029            var totalLegendLabel = this._makeLegendElement(WebInspector.UIString("Total"), this.calculator.formatValue(graphInfo.total));
1030            totalLegendLabel.addStyleClass("total");
1031            this.legendElement.appendChild(totalLegendLabel);
1032        }
1033
1034        this._drawSummaryGraph(fillSegments);
1035    },
1036
1037    _updateDividersLabelBarPosition: function()
1038    {
1039        var scrollTop = this.containerElement.scrollTop;
1040        var dividersTop = (scrollTop < this.summaryElement.offsetHeight ? this.summaryElement.offsetHeight : scrollTop);
1041        this.dividersElement.style.top = scrollTop + "px";
1042        this.dividersLabelBarElement.style.top = dividersTop + "px";
1043    },
1044
1045    _graphSelected: function(treeElement)
1046    {
1047        if (this._lastSelectedGraphTreeElement)
1048            this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = this.sortingSelectElement.selectedIndex;
1049
1050        this._lastSelectedGraphTreeElement = treeElement;
1051
1052        this.sortingSelectElement.removeChildren();
1053        for (var i = 0; i < treeElement.sortingOptions.length; ++i) {
1054            var sortingOption = treeElement.sortingOptions[i];
1055            var option = document.createElement("option");
1056            option.label = sortingOption.name;
1057            option.sortingFunction = sortingOption.sortingFunction;
1058            option.calculator = sortingOption.calculator;
1059            this.sortingSelectElement.appendChild(option);
1060        }
1061
1062        this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex;
1063        this._changeSortingFunction();
1064
1065        this.closeVisibleResource();
1066        this.containerElement.scrollTop = 0;
1067    },
1068
1069    _toggleLargerResources: function()
1070    {
1071        if (!this.resourcesTreeElement._childrenListNode)
1072            return;
1073
1074        this.resourcesTreeElement.smallChildren = !this.resourcesTreeElement.smallChildren;
1075
1076        if (this.resourcesTreeElement.smallChildren) {
1077            this.resourcesGraphsElement.addStyleClass("small");
1078            this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
1079            this.largerResourcesButton.removeStyleClass("toggled-on");
1080            this._adjustScrollPosition();
1081        } else {
1082            this.resourcesGraphsElement.removeStyleClass("small");
1083            this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
1084            this.largerResourcesButton.addStyleClass("toggled-on");
1085        }
1086    },
1087
1088    _adjustScrollPosition: function()
1089    {
1090        // Prevent the container from being scrolled off the end.
1091        if ((this.containerElement.scrollTop + this.containerElement.offsetHeight) > this.sidebarElement.offsetHeight)
1092            this.containerElement.scrollTop = (this.sidebarElement.offsetHeight - this.containerElement.offsetHeight);
1093    },
1094
1095    _changeSortingFunction: function()
1096    {
1097        var selectedOption = this.sortingSelectElement[this.sortingSelectElement.selectedIndex];
1098        this.sortingFunction = selectedOption.sortingFunction;
1099        this.calculator = selectedOption.calculator;
1100    },
1101
1102    _createResourceView: function(resource)
1103    {
1104        switch (resource.category) {
1105            case WebInspector.resourceCategories.documents:
1106            case WebInspector.resourceCategories.stylesheets:
1107            case WebInspector.resourceCategories.scripts:
1108            case WebInspector.resourceCategories.xhr:
1109                return new WebInspector.SourceView(resource);
1110            case WebInspector.resourceCategories.images:
1111                return new WebInspector.ImageView(resource);
1112            case WebInspector.resourceCategories.fonts:
1113                return new WebInspector.FontView(resource);
1114            default:
1115                return new WebInspector.ResourceView(resource);
1116        }
1117    },
1118
1119    _startSidebarDragging: function(event)
1120    {
1121        WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize");
1122    },
1123
1124    _sidebarDragging: function(event)
1125    {
1126        this._updateSidebarWidth(event.pageX);
1127
1128        event.preventDefault();
1129    },
1130
1131    _endSidebarDragging: function(event)
1132    {
1133        WebInspector.elementDragEnd(event);
1134    },
1135
1136    _updateSidebarWidth: function(width)
1137    {
1138        if (this.sidebarElement.offsetWidth <= 0) {
1139            // The stylesheet hasn't loaded yet or the window is closed,
1140            // so we can't calculate what is need. Return early.
1141            return;
1142        }
1143
1144        if (!("_currentSidebarWidth" in this))
1145            this._currentSidebarWidth = this.sidebarElement.offsetWidth;
1146
1147        if (typeof width === "undefined")
1148            width = this._currentSidebarWidth;
1149
1150        width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2);
1151
1152        this._currentSidebarWidth = width;
1153
1154        if (this.visibleResource) {
1155            this.containerElement.style.width = width + "px";
1156            this.sidebarElement.style.removeProperty("width");
1157        } else {
1158            this.sidebarElement.style.width = width + "px";
1159            this.containerElement.style.removeProperty("width");
1160        }
1161
1162        this.containerContentElement.style.left = width + "px";
1163        this.viewsContainerElement.style.left = width + "px";
1164        this.sidebarResizeElement.style.left = (width - 3) + "px";
1165
1166        this._updateGraphDividersIfNeeded();
1167
1168        var visibleView = this.visibleView;
1169        if (visibleView && "resize" in visibleView)
1170            visibleView.resize();
1171    },
1172
1173    _enableResourceTracking: function()
1174    {
1175        if (InspectorController.resourceTrackingEnabled())
1176            return;
1177        this._toggleResourceTracking(this.panelEnablerView.alwaysEnabled);
1178    },
1179
1180    _toggleResourceTracking: function(optionalAlways)
1181    {
1182        if (InspectorController.resourceTrackingEnabled()) {
1183            this.largerResourcesButton.visible = false;
1184            this.sortingSelectElement.visible = false;
1185            InspectorController.disableResourceTracking(true);
1186        } else {
1187            this.largerResourcesButton.visible = true;
1188            this.sortingSelectElement.visible = true;
1189            InspectorController.enableResourceTracking(!!optionalAlways);
1190        }
1191    }
1192}
1193
1194WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
1195
1196WebInspector.ResourceCalculator = function()
1197{
1198}
1199
1200WebInspector.ResourceCalculator.prototype = {
1201    computeSummaryValues: function(resources)
1202    {
1203        var total = 0;
1204        var categoryValues = {};
1205
1206        var resourcesLength = resources.length;
1207        for (var i = 0; i < resourcesLength; ++i) {
1208            var resource = resources[i];
1209            var value = this._value(resource);
1210            if (typeof value === "undefined")
1211                continue;
1212            if (!(resource.category.name in categoryValues))
1213                categoryValues[resource.category.name] = 0;
1214            categoryValues[resource.category.name] += value;
1215            total += value;
1216        }
1217
1218        return {categoryValues: categoryValues, total: total};
1219    },
1220
1221    computeBarGraphPercentages: function(resource)
1222    {
1223        return {start: 0, middle: 0, end: (this._value(resource) / this.boundarySpan) * 100};
1224    },
1225
1226    computeBarGraphLabels: function(resource)
1227    {
1228        const label = this.formatValue(this._value(resource));
1229        var tooltip = label;
1230        if (resource.cached)
1231            tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1232        return {left: label, right: label, tooltip: tooltip};
1233    },
1234
1235    get boundarySpan()
1236    {
1237        return this.maximumBoundary - this.minimumBoundary;
1238    },
1239
1240    updateBoundaries: function(resource)
1241    {
1242        this.minimumBoundary = 0;
1243
1244        var value = this._value(resource);
1245        if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
1246            this.maximumBoundary = value;
1247            return true;
1248        }
1249
1250        return false;
1251    },
1252
1253    reset: function()
1254    {
1255        delete this.minimumBoundary;
1256        delete this.maximumBoundary;
1257    },
1258
1259    _value: function(resource)
1260    {
1261        return 0;
1262    },
1263
1264    formatValue: function(value)
1265    {
1266        return value.toString();
1267    }
1268}
1269
1270WebInspector.ResourceTimeCalculator = function(startAtZero)
1271{
1272    WebInspector.ResourceCalculator.call(this);
1273    this.startAtZero = startAtZero;
1274}
1275
1276WebInspector.ResourceTimeCalculator.prototype = {
1277    computeSummaryValues: function(resources)
1278    {
1279        var resourcesByCategory = {};
1280        var resourcesLength = resources.length;
1281        for (var i = 0; i < resourcesLength; ++i) {
1282            var resource = resources[i];
1283            if (!(resource.category.name in resourcesByCategory))
1284                resourcesByCategory[resource.category.name] = [];
1285            resourcesByCategory[resource.category.name].push(resource);
1286        }
1287
1288        var earliestStart;
1289        var latestEnd;
1290        var categoryValues = {};
1291        for (var category in resourcesByCategory) {
1292            resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
1293            categoryValues[category] = 0;
1294
1295            var segment = {start: -1, end: -1};
1296
1297            var categoryResources = resourcesByCategory[category];
1298            var resourcesLength = categoryResources.length;
1299            for (var i = 0; i < resourcesLength; ++i) {
1300                var resource = categoryResources[i];
1301                if (resource.startTime === -1 || resource.endTime === -1)
1302                    continue;
1303
1304                if (typeof earliestStart === "undefined")
1305                    earliestStart = resource.startTime;
1306                else
1307                    earliestStart = Math.min(earliestStart, resource.startTime);
1308
1309                if (typeof latestEnd === "undefined")
1310                    latestEnd = resource.endTime;
1311                else
1312                    latestEnd = Math.max(latestEnd, resource.endTime);
1313
1314                if (resource.startTime <= segment.end) {
1315                    segment.end = Math.max(segment.end, resource.endTime);
1316                    continue;
1317                }
1318
1319                categoryValues[category] += segment.end - segment.start;
1320
1321                segment.start = resource.startTime;
1322                segment.end = resource.endTime;
1323            }
1324
1325            // Add the last segment
1326            categoryValues[category] += segment.end - segment.start;
1327        }
1328
1329        return {categoryValues: categoryValues, total: latestEnd - earliestStart};
1330    },
1331
1332    computeBarGraphPercentages: function(resource)
1333    {
1334        if (resource.startTime !== -1)
1335            var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
1336        else
1337            var start = 0;
1338
1339        if (resource.responseReceivedTime !== -1)
1340            var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
1341        else
1342            var middle = (this.startAtZero ? start : 100);
1343
1344        if (resource.endTime !== -1)
1345            var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
1346        else
1347            var end = (this.startAtZero ? middle : 100);
1348
1349        if (this.startAtZero) {
1350            end -= start;
1351            middle -= start;
1352            start = 0;
1353        }
1354
1355        return {start: start, middle: middle, end: end};
1356    },
1357
1358    computeBarGraphLabels: function(resource)
1359    {
1360        var leftLabel = "";
1361        if (resource.latency > 0)
1362            leftLabel = this.formatValue(resource.latency);
1363
1364        var rightLabel = "";
1365        if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
1366            rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
1367
1368        if (leftLabel && rightLabel) {
1369            var total = this.formatValue(resource.duration);
1370            var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
1371        } else if (leftLabel)
1372            var tooltip = WebInspector.UIString("%s latency", leftLabel);
1373        else if (rightLabel)
1374            var tooltip = WebInspector.UIString("%s download", rightLabel);
1375
1376        if (resource.cached)
1377            tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1378
1379        return {left: leftLabel, right: rightLabel, tooltip: tooltip};
1380    },
1381
1382    updateBoundaries: function(resource)
1383    {
1384        var didChange = false;
1385
1386        var lowerBound;
1387        if (this.startAtZero)
1388            lowerBound = 0;
1389        else
1390            lowerBound = this._lowerBound(resource);
1391
1392        if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
1393            this.minimumBoundary = lowerBound;
1394            didChange = true;
1395        }
1396
1397        var upperBound = this._upperBound(resource);
1398        if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
1399            this.maximumBoundary = upperBound;
1400            didChange = true;
1401        }
1402
1403        return didChange;
1404    },
1405
1406    formatValue: function(value)
1407    {
1408        return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
1409    },
1410
1411    _lowerBound: function(resource)
1412    {
1413        return 0;
1414    },
1415
1416    _upperBound: function(resource)
1417    {
1418        return 0;
1419    },
1420}
1421
1422WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype;
1423
1424WebInspector.ResourceTransferTimeCalculator = function()
1425{
1426    WebInspector.ResourceTimeCalculator.call(this, false);
1427}
1428
1429WebInspector.ResourceTransferTimeCalculator.prototype = {
1430    formatValue: function(value)
1431    {
1432        return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
1433    },
1434
1435    _lowerBound: function(resource)
1436    {
1437        return resource.startTime;
1438    },
1439
1440    _upperBound: function(resource)
1441    {
1442        return resource.endTime;
1443    }
1444}
1445
1446WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
1447
1448WebInspector.ResourceTransferDurationCalculator = function()
1449{
1450    WebInspector.ResourceTimeCalculator.call(this, true);
1451}
1452
1453WebInspector.ResourceTransferDurationCalculator.prototype = {
1454    formatValue: function(value)
1455    {
1456        return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector));
1457    },
1458
1459    _upperBound: function(resource)
1460    {
1461        return resource.duration;
1462    }
1463}
1464
1465WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
1466
1467WebInspector.ResourceTransferSizeCalculator = function()
1468{
1469    WebInspector.ResourceCalculator.call(this);
1470}
1471
1472WebInspector.ResourceTransferSizeCalculator.prototype = {
1473    _value: function(resource)
1474    {
1475        return resource.contentLength;
1476    },
1477
1478    formatValue: function(value)
1479    {
1480        return Number.bytesToString(value, WebInspector.UIString.bind(WebInspector));
1481    }
1482}
1483
1484WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype;
1485
1486WebInspector.ResourceSidebarTreeElement = function(resource)
1487{
1488    this.resource = resource;
1489
1490    this.createIconElement();
1491
1492    WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource);
1493
1494    this.refreshTitles();
1495}
1496
1497WebInspector.ResourceSidebarTreeElement.prototype = {
1498    onattach: function()
1499    {
1500        WebInspector.SidebarTreeElement.prototype.onattach.call(this);
1501
1502        var link = document.createElement("a");
1503        link.href = this.resource.url;
1504        link.className = "invisible";
1505        while (this._listItemNode.firstChild)
1506            link.appendChild(this._listItemNode.firstChild);
1507        this._listItemNode.appendChild(link);
1508        this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
1509    },
1510
1511    onselect: function()
1512    {
1513        WebInspector.panels.resources.showResource(this.resource);
1514    },
1515
1516    ondblclick: function(treeElement, event)
1517    {
1518        InspectorController.inspectedWindow().open(this.resource.url);
1519    },
1520
1521    get mainTitle()
1522    {
1523        return this.resource.displayName;
1524    },
1525
1526    set mainTitle(x)
1527    {
1528        // Do nothing.
1529    },
1530
1531    get subtitle()
1532    {
1533        var subtitle = this.resource.displayDomain;
1534
1535        if (this.resource.path && this.resource.lastPathComponent) {
1536            var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent);
1537            if (lastPathComponentIndex != -1)
1538                subtitle += this.resource.path.substring(0, lastPathComponentIndex);
1539        }
1540
1541        return subtitle;
1542    },
1543
1544    set subtitle(x)
1545    {
1546        // Do nothing.
1547    },
1548
1549    createIconElement: function()
1550    {
1551        var previousIconElement = this.iconElement;
1552
1553        if (this.resource.category === WebInspector.resourceCategories.images) {
1554            var previewImage = document.createElement("img");
1555            previewImage.className = "image-resource-icon-preview";
1556            previewImage.src = this.resource.url;
1557
1558            this.iconElement = document.createElement("div");
1559            this.iconElement.className = "icon";
1560            this.iconElement.appendChild(previewImage);
1561        } else {
1562            this.iconElement = document.createElement("img");
1563            this.iconElement.className = "icon";
1564        }
1565
1566        if (previousIconElement)
1567            previousIconElement.parentNode.replaceChild(this.iconElement, previousIconElement);
1568    },
1569
1570    refresh: function()
1571    {
1572        this.refreshTitles();
1573
1574        if (!this._listItemNode.hasStyleClass("resources-category-" + this.resource.category.name)) {
1575            this._listItemNode.removeMatchingStyleClasses("resources-category-\\w+");
1576            this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name);
1577
1578            this.createIconElement();
1579        }
1580    },
1581
1582    resetBubble: function()
1583    {
1584        this.bubbleText = "";
1585        this.bubbleElement.removeStyleClass("search-matches");
1586        this.bubbleElement.removeStyleClass("warning");
1587        this.bubbleElement.removeStyleClass("error");
1588    },
1589
1590    set searchMatches(matches)
1591    {
1592        this.resetBubble();
1593
1594        if (!matches)
1595            return;
1596
1597        this.bubbleText = matches;
1598        this.bubbleElement.addStyleClass("search-matches");
1599    },
1600
1601    updateErrorsAndWarnings: function()
1602    {
1603        this.resetBubble();
1604
1605        if (this.resource.warnings || this.resource.errors)
1606            this.bubbleText = (this.resource.warnings + this.resource.errors);
1607
1608        if (this.resource.warnings)
1609            this.bubbleElement.addStyleClass("warning");
1610
1611        if (this.resource.errors)
1612            this.bubbleElement.addStyleClass("error");
1613    }
1614}
1615
1616WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b)
1617{
1618    return WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1619        || WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
1620        || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
1621}
1622
1623WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b)
1624{
1625    return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource)
1626        || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1627        || WebInspector.Resource.CompareByEndTime(a.resource, b.resource);
1628}
1629
1630WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b)
1631{
1632    return WebInspector.Resource.CompareByEndTime(a.resource, b.resource)
1633        || WebInspector.Resource.CompareByStartTime(a.resource, b.resource)
1634        || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource);
1635}
1636
1637WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b)
1638{
1639    return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource);
1640}
1641
1642WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b)
1643{
1644    return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource);
1645}
1646
1647WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b)
1648{
1649    return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource);
1650}
1651
1652WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
1653
1654WebInspector.ResourceGraph = function(resource)
1655{
1656    this.resource = resource;
1657
1658    this._graphElement = document.createElement("div");
1659    this._graphElement.className = "resources-graph-side";
1660    this._graphElement.addEventListener("mouseover", this.refreshLabelPositions.bind(this), false);
1661
1662    if (resource.cached)
1663        this._graphElement.addStyleClass("resource-cached");
1664
1665    this._barAreaElement = document.createElement("div");
1666    this._barAreaElement.className = "resources-graph-bar-area hidden";
1667    this._graphElement.appendChild(this._barAreaElement);
1668
1669    this._barLeftElement = document.createElement("div");
1670    this._barLeftElement.className = "resources-graph-bar waiting";
1671    this._barAreaElement.appendChild(this._barLeftElement);
1672
1673    this._barRightElement = document.createElement("div");
1674    this._barRightElement.className = "resources-graph-bar";
1675    this._barAreaElement.appendChild(this._barRightElement);
1676
1677    this._labelLeftElement = document.createElement("div");
1678    this._labelLeftElement.className = "resources-graph-label waiting";
1679    this._barAreaElement.appendChild(this._labelLeftElement);
1680
1681    this._labelRightElement = document.createElement("div");
1682    this._labelRightElement.className = "resources-graph-label";
1683    this._barAreaElement.appendChild(this._labelRightElement);
1684
1685    this._graphElement.addStyleClass("resources-category-" + resource.category.name);
1686}
1687
1688WebInspector.ResourceGraph.prototype = {
1689    get graphElement()
1690    {
1691        return this._graphElement;
1692    },
1693
1694    refreshLabelPositions: function()
1695    {
1696        this._labelLeftElement.style.removeProperty("left");
1697        this._labelLeftElement.style.removeProperty("right");
1698        this._labelLeftElement.removeStyleClass("before");
1699        this._labelLeftElement.removeStyleClass("hidden");
1700
1701        this._labelRightElement.style.removeProperty("left");
1702        this._labelRightElement.style.removeProperty("right");
1703        this._labelRightElement.removeStyleClass("after");
1704        this._labelRightElement.removeStyleClass("hidden");
1705
1706        const labelPadding = 10;
1707        const rightBarWidth = (this._barRightElement.offsetWidth - labelPadding);
1708        const leftBarWidth = ((this._barLeftElement.offsetWidth - this._barRightElement.offsetWidth) - labelPadding);
1709
1710        var labelBefore = (this._labelLeftElement.offsetWidth > leftBarWidth);
1711        var labelAfter = (this._labelRightElement.offsetWidth > rightBarWidth);
1712
1713        if (labelBefore) {
1714            if ((this._graphElement.offsetWidth * (this._percentages.start / 100)) < (this._labelLeftElement.offsetWidth + 10))
1715                this._labelLeftElement.addStyleClass("hidden");
1716            this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
1717            this._labelLeftElement.addStyleClass("before");
1718        } else {
1719            this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
1720            this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
1721        }
1722
1723        if (labelAfter) {
1724            if ((this._graphElement.offsetWidth * ((100 - this._percentages.end) / 100)) < (this._labelRightElement.offsetWidth + 10))
1725                this._labelRightElement.addStyleClass("hidden");
1726            this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
1727            this._labelRightElement.addStyleClass("after");
1728        } else {
1729            this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
1730            this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
1731        }
1732    },
1733
1734    refresh: function(calculator)
1735    {
1736        var percentages = calculator.computeBarGraphPercentages(this.resource);
1737        var labels = calculator.computeBarGraphLabels(this.resource);
1738
1739        this._percentages = percentages;
1740
1741        this._barAreaElement.removeStyleClass("hidden");
1742
1743        if (!this._graphElement.hasStyleClass("resources-category-" + this.resource.category.name)) {
1744            this._graphElement.removeMatchingStyleClasses("resources-category-\\w+");
1745            this._graphElement.addStyleClass("resources-category-" + this.resource.category.name);
1746        }
1747
1748        this._barLeftElement.style.setProperty("left", percentages.start + "%");
1749        this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
1750
1751        this._barRightElement.style.setProperty("left", percentages.middle + "%");
1752        this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
1753
1754        this._labelLeftElement.textContent = labels.left;
1755        this._labelRightElement.textContent = labels.right;
1756
1757        var tooltip = (labels.tooltip || "");
1758        this._barLeftElement.title = tooltip;
1759        this._labelLeftElement.title = tooltip;
1760        this._labelRightElement.title = tooltip;
1761        this._barRightElement.title = tooltip;
1762    }
1763}
1764