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