Home | History | Annotate | Download | only in front-end
      1 /*
      2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
      3  * Copyright (C) 2008, 2009 Anthony Ricaud <rik (at) webkit.org>
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  *
      9  * 1.  Redistributions of source code must retain the above copyright
     10  *     notice, this list of conditions and the following disclaimer.
     11  * 2.  Redistributions in binary form must reproduce the above copyright
     12  *     notice, this list of conditions and the following disclaimer in the
     13  *     documentation and/or other materials provided with the distribution.
     14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     15  *     its contributors may be used to endorse or promote products derived
     16  *     from this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 WebInspector.ResourcesPanel = function()
     31 {
     32     WebInspector.AbstractTimelinePanel.call(this);
     33 
     34     this.element.addStyleClass("resources");
     35 
     36     this._createPanelEnabler();
     37 
     38     this.viewsContainerElement = document.createElement("div");
     39     this.viewsContainerElement.id = "resource-views";
     40     this.element.appendChild(this.viewsContainerElement);
     41 
     42     this.createFilterPanel();
     43     this.createInterface();
     44 
     45     this._createStatusbarButtons();
     46 
     47     this.reset();
     48     this.filter(this.filterAllElement, false);
     49     this.graphsTreeElement.children[0].select();
     50 }
     51 
     52 WebInspector.ResourcesPanel.prototype = {
     53     toolbarItemClass: "resources",
     54 
     55     get toolbarItemLabel()
     56     {
     57         return WebInspector.UIString("Resources");
     58     },
     59 
     60     get statusBarItems()
     61     {
     62         return [this.enableToggleButton.element, this.largerResourcesButton.element, this.sortingSelectElement];
     63     },
     64 
     65     get categories()
     66     {
     67         return WebInspector.resourceCategories;
     68     },
     69 
     70     createItemTreeElement: function(item)
     71     {
     72         return new WebInspector.ResourceSidebarTreeElement(item);
     73     },
     74 
     75     createItemGraph: function(item)
     76     {
     77         return new WebInspector.ResourceGraph(item);
     78     },
     79 
     80     isCategoryVisible: function(categoryName)
     81     {
     82         return (this.itemsGraphsElement.hasStyleClass("filter-all") || this.itemsGraphsElement.hasStyleClass("filter-" + categoryName.toLowerCase()));
     83     },
     84 
     85     populateSidebar: function()
     86     {
     87         var timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time"));
     88         timeGraphItem.onselect = this._graphSelected.bind(this);
     89 
     90         var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator();
     91         var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator();
     92 
     93         timeGraphItem.sortingOptions = [
     94             { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator },
     95             { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator },
     96             { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator },
     97             { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator },
     98             { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator },
     99         ];
    100 
    101         timeGraphItem.selectedSortingOptionIndex = 1;
    102 
    103         var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size"));
    104         sizeGraphItem.onselect = this._graphSelected.bind(this);
    105 
    106         var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator();
    107         sizeGraphItem.sortingOptions = [
    108             { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator },
    109         ];
    110 
    111         sizeGraphItem.selectedSortingOptionIndex = 0;
    112 
    113         this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true);
    114         this.sidebarTree.appendChild(this.graphsTreeElement);
    115 
    116         this.graphsTreeElement.appendChild(timeGraphItem);
    117         this.graphsTreeElement.appendChild(sizeGraphItem);
    118         this.graphsTreeElement.expand();
    119 
    120         this.itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true);
    121         this.sidebarTree.appendChild(this.itemsTreeElement);
    122 
    123         this.itemsTreeElement.expand();
    124     },
    125 
    126     _createPanelEnabler: function()
    127     {
    128         var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel.");
    129         var panelEnablerDisclaimer = WebInspector.UIString("Enabling resource tracking will reload the page and make page loading slower.");
    130         var panelEnablerButton = WebInspector.UIString("Enable resource tracking");
    131 
    132         this.panelEnablerView = new WebInspector.PanelEnablerView("resources", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
    133         this.panelEnablerView.addEventListener("enable clicked", this._enableResourceTracking, this);
    134 
    135         this.element.appendChild(this.panelEnablerView.element);
    136 
    137         this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
    138         this.enableToggleButton.addEventListener("click", this._toggleResourceTracking.bind(this), false);
    139     },
    140 
    141     _createStatusbarButtons: function()
    142     {
    143         this.largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "resources-larger-resources-status-bar-item");
    144 
    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 
    712 WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.AbstractTimelinePanel.prototype;
    713 
    714 WebInspector.getResourceContent = function(identifier, callback)
    715 {
    716     InspectorBackend.getResourceContent(WebInspector.Callback.wrap(callback), identifier);
    717 }
    718 
    719 WebInspector.didGetResourceContent = WebInspector.Callback.processCallback;
    720 
    721 WebInspector.ResourceTimeCalculator = function(startAtZero)
    722 {
    723     WebInspector.AbstractTimelineCalculator.call(this);
    724     this.startAtZero = startAtZero;
    725 }
    726 
    727 WebInspector.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 
    884 WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;
    885 
    886 WebInspector.ResourceTransferTimeCalculator = function()
    887 {
    888     WebInspector.ResourceTimeCalculator.call(this, false);
    889 }
    890 
    891 WebInspector.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 
    908 WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
    909 
    910 WebInspector.ResourceTransferDurationCalculator = function()
    911 {
    912     WebInspector.ResourceTimeCalculator.call(this, true);
    913 }
    914 
    915 WebInspector.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 
    927 WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype;
    928 
    929 WebInspector.ResourceTransferSizeCalculator = function()
    930 {
    931     WebInspector.AbstractTimelineCalculator.call(this);
    932 }
    933 
    934 WebInspector.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 
    955 WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.AbstractTimelineCalculator.prototype;
    956 
    957 WebInspector.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 
    968 WebInspector.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 
   1101 WebInspector.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 
   1108 WebInspector.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 
   1115 WebInspector.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 
   1122 WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b)
   1123 {
   1124     return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource);
   1125 }
   1126 
   1127 WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b)
   1128 {
   1129     return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource);
   1130 }
   1131 
   1132 WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b)
   1133 {
   1134     return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource);
   1135 }
   1136 
   1137 WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
   1138 
   1139 WebInspector.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 
   1173 WebInspector.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