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  * Copyright (C) 2011 Google Inc. All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  *
     10  * 1.  Redistributions of source code must retain the above copyright
     11  *     notice, this list of conditions and the following disclaimer.
     12  * 2.  Redistributions in binary form must reproduce the above copyright
     13  *     notice, this list of conditions and the following disclaimer in the
     14  *     documentation and/or other materials provided with the distribution.
     15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     16  *     its contributors may be used to endorse or promote products derived
     17  *     from this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 WebInspector.NetworkPanel = function()
     32 {
     33     WebInspector.Panel.call(this, "network");
     34 
     35     this.createSidebar();
     36     this.sidebarElement.className = "network-sidebar";
     37 
     38     this._resources = [];
     39     this._resourcesById = {};
     40     this._resourcesByURL = {};
     41     this._staleResources = [];
     42     this._resourceGridNodes = {};
     43     this._lastResourceGridNodeId = 0;
     44     this._mainResourceLoadTime = -1;
     45     this._mainResourceDOMContentTime = -1;
     46     this._hiddenCategories = {};
     47 
     48     this._categories = WebInspector.resourceCategories;
     49 
     50     this.containerElement = document.createElement("div");
     51     this.containerElement.id = "network-container";
     52     this.sidebarElement.appendChild(this.containerElement);
     53 
     54     this._viewsContainerElement = document.createElement("div");
     55     this._viewsContainerElement.id = "network-views";
     56     this._viewsContainerElement.className = "hidden";
     57     this.element.appendChild(this._viewsContainerElement);
     58 
     59     this._closeButtonElement = document.createElement("button");
     60     this._closeButtonElement.id = "network-close-button";
     61     this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false);
     62     this._viewsContainerElement.appendChild(this._closeButtonElement);
     63 
     64     this._createSortingFunctions();
     65     this._createTable();
     66     this._createTimelineGrid();
     67     this._createStatusbarButtons();
     68     this._createFilterStatusBarItems();
     69     this._createSummaryBar();
     70 
     71     if (!WebInspector.settings.resourcesLargeRows)
     72         this._setLargerResources(WebInspector.settings.resourcesLargeRows);
     73 
     74     this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true);
     75     // Enable faster hint.
     76     this._popoverHelper.setTimeout(100);
     77 
     78     this.calculator = new WebInspector.NetworkTransferTimeCalculator();
     79     this._filter(this._filterAllElement, false);
     80 
     81     this._toggleGridMode();
     82 
     83     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceStarted, this._onResourceStarted, this);
     84     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdated, this._onResourceUpdated, this);
     85     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceFinished, this._onResourceUpdated, this);
     86     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.FrameCommittedLoad, this._onFrameCommitLoad, this);
     87 }
     88 
     89 WebInspector.NetworkPanel.prototype = {
     90     get toolbarItemLabel()
     91     {
     92         return WebInspector.UIString("Network");
     93     },
     94 
     95     get statusBarItems()
     96     {
     97         return [this._largerResourcesButton.element, this._preserveLogToggle.element, this._clearButton.element, this._filterBarElement];
     98     },
     99 
    100     isCategoryVisible: function(categoryName)
    101     {
    102         return true;
    103     },
    104 
    105     elementsToRestoreScrollPositionsFor: function()
    106     {
    107         return [this.containerElement, this._dataGrid.scrollContainer];
    108     },
    109 
    110     resize: function()
    111     {
    112         WebInspector.Panel.prototype.resize.call(this);
    113         this._dataGrid.updateWidths();
    114         this._updateOffscreenRows();
    115     },
    116 
    117     updateSidebarWidth: function(width)
    118     {
    119         if (!this._viewingResourceMode)
    120             return;
    121         WebInspector.Panel.prototype.updateSidebarWidth.call(this, width);
    122     },
    123 
    124     updateMainViewWidth: function(width)
    125     {
    126         this._viewsContainerElement.style.left = width + "px";
    127     },
    128 
    129     handleShortcut: function(event)
    130     {
    131         if (this._viewingResourceMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
    132             this._toggleGridMode();
    133             event.handled = true;
    134         }
    135     },
    136 
    137     _createTimelineGrid: function()
    138     {
    139         this._timelineGrid = new WebInspector.TimelineGrid();
    140         this._timelineGrid.element.addStyleClass("network-timeline-grid");
    141         this._dataGrid.element.appendChild(this._timelineGrid.element);
    142     },
    143 
    144     _createTable: function()
    145     {
    146         var columns = {name: {}, method: {}, status: {}, type: {}, size: {}, time: {}, timeline: {}};
    147         columns.name.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path"));
    148         columns.name.sortable = true;
    149         columns.name.width = "20%";
    150         columns.name.disclosure = true;
    151 
    152         columns.method.title = WebInspector.UIString("Method");
    153         columns.method.sortable = true;
    154         columns.method.width = "7%";
    155 
    156         columns.status.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text"));
    157         columns.status.sortable = true;
    158         columns.status.width = "8%";
    159 
    160         columns.type.title = WebInspector.UIString("Type");
    161         columns.type.sortable = true;
    162         columns.type.width = "10%";
    163 
    164         columns.size.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Transfer"));
    165         columns.size.sortable = true;
    166         columns.size.width = "10%";
    167         columns.size.aligned = "right";
    168 
    169         columns.time.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Latency"));
    170         columns.time.sortable = true;
    171         columns.time.width = "10%";
    172         columns.time.aligned = "right";
    173 
    174         columns.timeline.title = "";
    175         columns.timeline.sortable = false;
    176         columns.timeline.width = "37%";
    177         columns.timeline.sort = "ascending";
    178 
    179         this._dataGrid = new WebInspector.DataGrid(columns);
    180         this._dataGrid.element.addEventListener("contextmenu", this._contextMenu.bind(this), true);
    181         this.containerElement.appendChild(this._dataGrid.element);
    182         this._dataGrid.addEventListener("sorting changed", this._sortItems, this);
    183         this._dataGrid.addEventListener("width changed", this._updateDividersIfNeeded, this);
    184         this._dataGrid.scrollContainer.addEventListener("scroll", this._updateOffscreenRows.bind(this));
    185 
    186         this._patchTimelineHeader();
    187     },
    188 
    189     _makeHeaderFragment: function(title, subtitle)
    190     {
    191         var fragment = document.createDocumentFragment();
    192         fragment.appendChild(document.createTextNode(title));
    193         var subtitleDiv = document.createElement("div");
    194         subtitleDiv.className = "network-header-subtitle";
    195         subtitleDiv.textContent = subtitle;
    196         fragment.appendChild(subtitleDiv);
    197         return fragment;
    198     },
    199 
    200     _patchTimelineHeader: function()
    201     {
    202         var timelineSorting = document.createElement("select");
    203 
    204         var option = document.createElement("option");
    205         option.value = "startTime";
    206         option.label = WebInspector.UIString("Timeline");
    207         timelineSorting.appendChild(option);
    208 
    209         option = document.createElement("option");
    210         option.value = "startTime";
    211         option.label = WebInspector.UIString("Start Time");
    212         timelineSorting.appendChild(option);
    213 
    214         option = document.createElement("option");
    215         option.value = "responseTime";
    216         option.label = WebInspector.UIString("Response Time");
    217         timelineSorting.appendChild(option);
    218 
    219         option = document.createElement("option");
    220         option.value = "endTime";
    221         option.label = WebInspector.UIString("End Time");
    222         timelineSorting.appendChild(option);
    223 
    224         option = document.createElement("option");
    225         option.value = "duration";
    226         option.label = WebInspector.UIString("Duration");
    227         timelineSorting.appendChild(option);
    228 
    229         option = document.createElement("option");
    230         option.value = "latency";
    231         option.label = WebInspector.UIString("Latency");
    232         timelineSorting.appendChild(option);
    233 
    234         var header = this._dataGrid.headerTableHeader("timeline");
    235         header.replaceChild(timelineSorting, header.firstChild);
    236 
    237         timelineSorting.addEventListener("click", function(event) { event.stopPropagation() }, false);
    238         timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false);
    239         this._timelineSortSelector = timelineSorting;
    240     },
    241 
    242     _createSortingFunctions: function()
    243     {
    244         this._sortingFunctions = {};
    245         this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator;
    246         this._sortingFunctions.method = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "method", false);
    247         this._sortingFunctions.status = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "statusCode", false);
    248         this._sortingFunctions.type = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "mimeType", false);
    249         this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator;
    250         this._sortingFunctions.time = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", false);
    251         this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false);
    252         this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false);
    253         this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "endTime", false);
    254         this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "responseReceivedTime", false);
    255         this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", true);
    256         this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "latency", true);
    257 
    258         var timeCalculator = new WebInspector.NetworkTransferTimeCalculator();
    259         var durationCalculator = new WebInspector.NetworkTransferDurationCalculator();
    260 
    261         this._calculators = {};
    262         this._calculators.timeline = timeCalculator;
    263         this._calculators.startTime = timeCalculator;
    264         this._calculators.endTime = timeCalculator;
    265         this._calculators.responseTime = timeCalculator;
    266         this._calculators.duration = durationCalculator;
    267         this._calculators.latency = durationCalculator;
    268     },
    269 
    270     _sortItems: function()
    271     {
    272         var columnIdentifier = this._dataGrid.sortColumnIdentifier;
    273         if (columnIdentifier === "timeline") {
    274             this._sortByTimeline();
    275             return;
    276         }
    277         var sortingFunction = this._sortingFunctions[columnIdentifier];
    278         if (!sortingFunction)
    279             return;
    280 
    281         this._dataGrid.sortNodes(sortingFunction, this._dataGrid.sortOrder === "descending");
    282         this._timelineSortSelector.selectedIndex = 0;
    283         this._updateOffscreenRows();
    284     },
    285 
    286     _sortByTimeline: function()
    287     {
    288         var selectedIndex = this._timelineSortSelector.selectedIndex;
    289         if (!selectedIndex)
    290             selectedIndex = 1; // Sort by start time by default.
    291         var selectedOption = this._timelineSortSelector[selectedIndex];
    292         var value = selectedOption.value;
    293 
    294         var sortingFunction = this._sortingFunctions[value];
    295         this._dataGrid.sortNodes(sortingFunction);
    296         this.calculator = this._calculators[value];
    297         if (this.calculator.startAtZero)
    298             this._timelineGrid.hideEventDividers();
    299         else
    300             this._timelineGrid.showEventDividers();
    301         this._dataGrid.markColumnAsSortedBy("timeline", "ascending");
    302         this._updateOffscreenRows();
    303     },
    304 
    305     _createFilterStatusBarItems: function()
    306     {
    307         var filterBarElement = document.createElement("div");
    308         filterBarElement.className = "scope-bar status-bar-item";
    309         filterBarElement.id = "network-filter";
    310 
    311         function createFilterElement(category, label)
    312         {
    313             var categoryElement = document.createElement("li");
    314             categoryElement.category = category;
    315             categoryElement.className = category;
    316             categoryElement.appendChild(document.createTextNode(label));
    317             categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
    318             filterBarElement.appendChild(categoryElement);
    319 
    320             return categoryElement;
    321         }
    322 
    323         this._filterAllElement = createFilterElement.call(this, "all", WebInspector.UIString("All"));
    324 
    325         // Add a divider
    326         var dividerElement = document.createElement("div");
    327         dividerElement.addStyleClass("scope-bar-divider");
    328         filterBarElement.appendChild(dividerElement);
    329 
    330         for (var category in this._categories)
    331             createFilterElement.call(this, category, this._categories[category].title);
    332         this._filterBarElement = filterBarElement;
    333     },
    334 
    335     _createSummaryBar: function()
    336     {
    337         var tbody = this._dataGrid.dataTableBody;
    338         var tfoot = document.createElement("tfoot");
    339         var tr = tfoot.createChild("tr", "revealed network-summary-bar");
    340         var td = tr.createChild("td");
    341         td.setAttribute("colspan", 7);
    342         tbody.parentNode.insertBefore(tfoot, tbody);
    343         this._summaryBarElement = td;
    344     },
    345 
    346     _updateSummaryBar: function()
    347     {
    348         var numRequests = this._resources.length;
    349 
    350         if (!numRequests) {
    351             if (this._summaryBarElement._isDisplayingWarning)
    352                 return;
    353             this._summaryBarElement._isDisplayingWarning = true;
    354 
    355             var img = document.createElement("img");
    356             img.src = "Images/warningIcon.png";
    357             this._summaryBarElement.removeChildren();
    358             this._summaryBarElement.appendChild(img);
    359             this._summaryBarElement.appendChild(document.createTextNode(
    360                 WebInspector.UIString("No requests captured. Reload the page to see detailed information on the network activity.")));
    361             return;
    362         }
    363         delete this._summaryBarElement._isDisplayingWarning;
    364 
    365         var transferSize = 0;
    366         var baseTime = -1;
    367         var maxTime = -1;
    368         for (var i = 0; i < this._resources.length; ++i) {
    369             var resource = this._resources[i];
    370             transferSize += (resource.cached || !resource.transferSize) ? 0 : resource.transferSize;
    371             if (resource === WebInspector.mainResource)
    372                 baseTime = resource.startTime;
    373             if (resource.endTime > maxTime)
    374                 maxTime = resource.endTime;
    375         }
    376         var text = String.sprintf(WebInspector.UIString("%d requests"), numRequests);
    377         text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize));
    378         if (baseTime !== -1 && this._mainResourceLoadTime !== -1 && this._mainResourceDOMContentTime !== -1 && this._mainResourceDOMContentTime > baseTime) {
    379             text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s (onload: %s, DOMContentLoaded: %s)"),
    380                         Number.secondsToString(maxTime - baseTime),
    381                         Number.secondsToString(this._mainResourceLoadTime - baseTime),
    382                         Number.secondsToString(this._mainResourceDOMContentTime - baseTime));
    383         }
    384         this._summaryBarElement.textContent = text;
    385     },
    386 
    387     _showCategory: function(category)
    388     {
    389         this._dataGrid.element.addStyleClass("filter-" + category);
    390         delete this._hiddenCategories[category];
    391     },
    392 
    393     _hideCategory: function(category)
    394     {
    395         this._dataGrid.element.removeStyleClass("filter-" + category);
    396         this._hiddenCategories[category] = true;
    397     },
    398 
    399     _updateFilter: function(e)
    400     {
    401         var isMac = WebInspector.isMac();
    402         var selectMultiple = false;
    403         if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
    404             selectMultiple = true;
    405         if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
    406             selectMultiple = true;
    407 
    408         this._filter(e.target, selectMultiple);
    409     },
    410 
    411     _filter: function(target, selectMultiple)
    412     {
    413         function unselectAll()
    414         {
    415             for (var i = 0; i < this._filterBarElement.childNodes.length; ++i) {
    416                 var child = this._filterBarElement.childNodes[i];
    417                 if (!child.category)
    418                     continue;
    419 
    420                 child.removeStyleClass("selected");
    421                 this._hideCategory(child.category);
    422             }
    423         }
    424 
    425         if (target.category === this._filterAllElement) {
    426             if (target.hasStyleClass("selected")) {
    427                 // We can't unselect All, so we break early here
    428                 return;
    429             }
    430 
    431             // If All wasn't selected, and now is, unselect everything else.
    432             unselectAll.call(this);
    433         } else {
    434             // Something other than All is being selected, so we want to unselect All.
    435             if (this._filterAllElement.hasStyleClass("selected")) {
    436                 this._filterAllElement.removeStyleClass("selected");
    437                 this._hideCategory("all");
    438             }
    439         }
    440 
    441         if (!selectMultiple) {
    442             // If multiple selection is off, we want to unselect everything else
    443             // and just select ourselves.
    444             unselectAll.call(this);
    445 
    446             target.addStyleClass("selected");
    447             this._showCategory(target.category);
    448             this._updateOffscreenRows();
    449             return;
    450         }
    451 
    452         if (target.hasStyleClass("selected")) {
    453             // If selectMultiple is turned on, and we were selected, we just
    454             // want to unselect ourselves.
    455             target.removeStyleClass("selected");
    456             this._hideCategory(target.category);
    457         } else {
    458             // If selectMultiple is turned on, and we weren't selected, we just
    459             // want to select ourselves.
    460             target.addStyleClass("selected");
    461             this._showCategory(target.category);
    462         }
    463         this._updateOffscreenRows();
    464     },
    465 
    466     _scheduleRefresh: function()
    467     {
    468         if (this._needsRefresh)
    469             return;
    470 
    471         this._needsRefresh = true;
    472 
    473         if (this.visible && !("_refreshTimeout" in this))
    474             this._refreshTimeout = setTimeout(this.refresh.bind(this), 500);
    475     },
    476 
    477     _updateDividersIfNeeded: function(force)
    478     {
    479         var timelineColumn = this._dataGrid.columns.timeline;
    480         for (var i = 0; i < this._dataGrid.resizers.length; ++i) {
    481             if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnID) {
    482                 // Position timline grid location.
    483                 this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left;
    484                 this._timelineGrid.element.style.right = "18px";
    485             }
    486         }
    487 
    488         var proceed = true;
    489         if (!this.visible) {
    490             this._scheduleRefresh();
    491             proceed = false;
    492         } else
    493             proceed = this._timelineGrid.updateDividers(force, this.calculator);
    494 
    495         if (!proceed)
    496             return;
    497 
    498         if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) {
    499             // If our current sorting method starts at zero, that means it shows all
    500             // resources starting at the same point, and so onLoad event and DOMContent
    501             // event lines really wouldn't make much sense here, so don't render them.
    502             // Additionally, if the calculator doesn't have the computePercentageFromEventTime
    503             // function defined, we are probably sorting by size, and event times aren't relevant
    504             // in this case.
    505             return;
    506         }
    507 
    508         this._timelineGrid.removeEventDividers();
    509         if (this._mainResourceLoadTime !== -1) {
    510             var percent = this.calculator.computePercentageFromEventTime(this._mainResourceLoadTime);
    511 
    512             var loadDivider = document.createElement("div");
    513             loadDivider.className = "network-event-divider network-red-divider";
    514 
    515             var loadDividerPadding = document.createElement("div");
    516             loadDividerPadding.className = "network-event-divider-padding";
    517             loadDividerPadding.title = WebInspector.UIString("Load event fired");
    518             loadDividerPadding.appendChild(loadDivider);
    519             loadDividerPadding.style.left = percent + "%";
    520             this._timelineGrid.addEventDivider(loadDividerPadding);
    521         }
    522 
    523         if (this._mainResourceDOMContentTime !== -1) {
    524             var percent = this.calculator.computePercentageFromEventTime(this._mainResourceDOMContentTime);
    525 
    526             var domContentDivider = document.createElement("div");
    527             domContentDivider.className = "network-event-divider network-blue-divider";
    528 
    529             var domContentDividerPadding = document.createElement("div");
    530             domContentDividerPadding.className = "network-event-divider-padding";
    531             domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired");
    532             domContentDividerPadding.appendChild(domContentDivider);
    533             domContentDividerPadding.style.left = percent + "%";
    534             this._timelineGrid.addEventDivider(domContentDividerPadding);
    535         }
    536     },
    537 
    538     _refreshIfNeeded: function()
    539     {
    540         if (this._needsRefresh)
    541             this.refresh();
    542     },
    543 
    544     _invalidateAllItems: function()
    545     {
    546         this._staleResources = this._resources.slice();
    547     },
    548 
    549     get calculator()
    550     {
    551         return this._calculator;
    552     },
    553 
    554     set calculator(x)
    555     {
    556         if (!x || this._calculator === x)
    557             return;
    558 
    559         this._calculator = x;
    560         this._calculator.reset();
    561 
    562         this._invalidateAllItems();
    563         this.refresh();
    564     },
    565 
    566     _resourceGridNode: function(resource)
    567     {
    568         return this._resourceGridNodes[resource.__gridNodeId];
    569     },
    570 
    571     _createResourceGridNode: function(resource)
    572     {
    573         var node = new WebInspector.NetworkDataGridNode(this, resource);
    574         resource.__gridNodeId = this._lastResourceGridNodeId++;
    575         this._resourceGridNodes[resource.__gridNodeId] = node;
    576         return node;
    577     },
    578 
    579     revealAndSelectItem: function(resource)
    580     {
    581         var node = this._resourceGridNode(resource);
    582         if (node) {
    583             node.reveal();
    584             node.select(true);
    585         }
    586     },
    587 
    588     addEventDivider: function(divider)
    589     {
    590         this._timelineGrid.addEventDivider(divider);
    591     },
    592 
    593     _createStatusbarButtons: function()
    594     {
    595         this._preserveLogToggle = new WebInspector.StatusBarButton(WebInspector.UIString("Preserve Log upon Navigation"), "record-profile-status-bar-item");
    596         this._preserveLogToggle.addEventListener("click", this._onPreserveLogClicked.bind(this), false);
    597 
    598         this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
    599         this._clearButton.addEventListener("click", this._reset.bind(this), false);
    600 
    601         this._largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item");
    602         this._largerResourcesButton.toggled = WebInspector.settings.resourcesLargeRows;
    603         this._largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
    604     },
    605 
    606     set mainResourceLoadTime(x)
    607     {
    608         if (this._mainResourceLoadTime === x)
    609             return;
    610 
    611         this._mainResourceLoadTime = x || -1;
    612         // Update the dividers to draw the new line
    613         this._updateDividersIfNeeded(true);
    614     },
    615 
    616     set mainResourceDOMContentTime(x)
    617     {
    618         if (this._mainResourceDOMContentTime === x)
    619             return;
    620 
    621         this._mainResourceDOMContentTime = x || -1;
    622         this._updateDividersIfNeeded(true);
    623     },
    624 
    625     show: function()
    626     {
    627         WebInspector.Panel.prototype.show.call(this);
    628         this._refreshIfNeeded();
    629 
    630         if (this.visibleView)
    631             this.visibleView.show(this._viewsContainerElement);
    632 
    633         this._dataGrid.updateWidths();
    634     },
    635 
    636     hide: function()
    637     {
    638         WebInspector.Panel.prototype.hide.call(this);
    639         this._popoverHelper.hidePopup();
    640     },
    641 
    642     get searchableViews()
    643     {
    644         var views = [];
    645         return views;
    646     },
    647 
    648     searchMatchFound: function(view, matches)
    649     {
    650         this._resourceGridNode(view.resource).searchMatches = matches;
    651     },
    652 
    653     searchCanceled: function(startingNewSearch)
    654     {
    655         WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
    656 
    657         if (startingNewSearch || !this._resources)
    658             return;
    659     },
    660 
    661     performSearch: function(query)
    662     {
    663         WebInspector.Panel.prototype.performSearch.call(this, query);
    664     },
    665 
    666     refresh: function()
    667     {
    668         this._needsRefresh = false;
    669         if ("_refreshTimeout" in this) {
    670             clearTimeout(this._refreshTimeout);
    671             delete this._refreshTimeout;
    672         }
    673 
    674         var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow();
    675         var staleItemsLength = this._staleResources.length;
    676         var boundariesChanged = false;
    677 
    678         for (var i = 0; i < staleItemsLength; ++i) {
    679             var resource = this._staleResources[i];
    680             var node = this._resourceGridNode(resource);
    681             if (!node) {
    682                 // Create the timeline tree element and graph.
    683                 node = this._createResourceGridNode(resource);
    684                 this._dataGrid.appendChild(node);
    685             }
    686             node.refreshResource();
    687 
    688             if (this.calculator.updateBoundaries(resource))
    689                 boundariesChanged = true;
    690         }
    691 
    692         if (boundariesChanged) {
    693             // The boundaries changed, so all item graphs are stale.
    694             this._invalidateAllItems();
    695             staleItemsLength = this._staleResources.length;
    696         }
    697 
    698         for (var i = 0; i < staleItemsLength; ++i)
    699             this._resourceGridNode(this._staleResources[i]).refreshGraph(this.calculator);
    700 
    701         this._staleResources = [];
    702         this._sortItems();
    703         this._updateSummaryBar();
    704         this._updateOffscreenRows();
    705         this._dataGrid.updateWidths();
    706 
    707         if (wasScrolledToLastRow)
    708             this._dataGrid.scrollToLastRow();
    709     },
    710 
    711     _onPreserveLogClicked: function(e)
    712     {
    713         this._preserveLogToggle.toggled = !this._preserveLogToggle.toggled;
    714     },
    715 
    716     _reset: function()
    717     {
    718         this._popoverHelper.hidePopup();
    719         this._closeVisibleResource();
    720 
    721         this._toggleGridMode();
    722 
    723         // Begin reset timeline
    724         if (this._calculator)
    725             this._calculator.reset();
    726 
    727         this._resources = [];
    728         this._resourcesById = {};
    729         this._resourcesByURL = {};
    730         this._staleResources = [];
    731         this._resourceGridNodes = {};
    732 
    733         this._dataGrid.removeChildren();
    734         this._updateDividersIfNeeded(true);
    735         // End reset timeline.
    736 
    737         this._mainResourceLoadTime = -1;
    738         this._mainResourceDOMContentTime = -1;
    739 
    740         this._viewsContainerElement.removeChildren();
    741         this._viewsContainerElement.appendChild(this._closeButtonElement);
    742         this._updateSummaryBar();
    743         WebInspector.extensionServer.resetResources();
    744     },
    745 
    746     get resources()
    747     {
    748         return this._resources;
    749     },
    750 
    751     resourceById: function(id)
    752     {
    753         return this._resourcesById[id];
    754     },
    755 
    756     _onResourceStarted: function(event)
    757     {
    758         this._appendResource(event.data);
    759     },
    760 
    761     _appendResource: function(resource)
    762     {
    763         this._resources.push(resource);
    764         this._resourcesById[resource.identifier] = resource;
    765         this._resourcesByURL[resource.url] = resource;
    766 
    767         // Pull all the redirects of the main resource upon commit load.
    768         if (resource.redirects) {
    769             for (var i = 0; i < resource.redirects.length; ++i)
    770                 this._refreshResource(resource.redirects[i]);
    771         }
    772 
    773         this._refreshResource(resource);
    774     },
    775 
    776     _onResourceUpdated: function(event)
    777     {
    778         this._refreshResource(event.data);
    779     },
    780 
    781     _refreshResource: function(resource)
    782     {
    783         this._staleResources.push(resource);
    784         this._scheduleRefresh();
    785 
    786         var oldView = WebInspector.ResourceView.existingResourceViewForResource(resource);
    787         if (!oldView)
    788             return;
    789 
    790         if (WebInspector.ResourceView.resourceViewTypeMatchesResource(resource))
    791             return;
    792 
    793         var newView = WebInspector.ResourceView.recreateResourceView(resource);
    794         if (this.visibleView === oldView)
    795             this.visibleView = newView;
    796     },
    797 
    798     clear: function()
    799     {
    800         if (this._preserveLogToggle.toggled)
    801             return;
    802         this._reset();
    803     },
    804 
    805     _onFrameCommitLoad: function(event)
    806     {
    807         if (event.data.frame.parentId)
    808             return;
    809 
    810         // Main frame committed load.
    811         if (this._preserveLogToggle.toggled)
    812             return;
    813 
    814         // Preserve provisional load resources.
    815         var loaderId = event.data.loaderId;
    816         var resourcesToPreserve = [];
    817         for (var i = 0; i < this._resources.length; ++i) {
    818             var resource = this._resources[i];
    819             if (resource.loaderId === loaderId)
    820                 resourcesToPreserve.push(resource);
    821         }
    822 
    823         this._reset();
    824 
    825         // Restore preserved items.
    826         for (var i = 0; i < resourcesToPreserve.length; ++i)
    827             this._appendResource(resourcesToPreserve[i]);
    828     },
    829 
    830     canShowAnchorLocation: function(anchor)
    831     {
    832         return !!this._resourcesByURL[anchor.href];
    833     },
    834 
    835     showAnchorLocation: function(anchor)
    836     {
    837         this._showResource(this._resourcesByURL[anchor.href], anchor.getAttribute("line_number") - 1);
    838     },
    839 
    840     _showResource: function(resource, line)
    841     {
    842         if (!resource)
    843             return;
    844 
    845         this._popoverHelper.hidePopup();
    846 
    847         this._toggleViewingResourceMode();
    848 
    849         if (this.visibleView) {
    850             this.visibleView.detach();
    851             delete this.visibleView;
    852         }
    853 
    854         var view = new WebInspector.NetworkItemView(resource);
    855         view.show(this._viewsContainerElement);
    856         this.visibleView = view;
    857 
    858         this.updateSidebarWidth();
    859     },
    860 
    861     _closeVisibleResource: function()
    862     {
    863         this.element.removeStyleClass("viewing-resource");
    864 
    865         if (this.visibleView) {
    866             this.visibleView.detach();
    867             delete this.visibleView;
    868         }
    869 
    870         if (this._lastSelectedGraphTreeElement)
    871             this._lastSelectedGraphTreeElement.select(true);
    872 
    873         this.updateSidebarWidth();
    874     },
    875 
    876     _toggleLargerResources: function()
    877     {
    878         WebInspector.settings.resourcesLargeRows = !WebInspector.settings.resourcesLargeRows;
    879         this._setLargerResources(WebInspector.settings.resourcesLargeRows);
    880     },
    881 
    882     _setLargerResources: function(enabled)
    883     {
    884         this._largerResourcesButton.toggled = enabled;
    885         if (!enabled) {
    886             this._largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
    887             this._dataGrid.element.addStyleClass("small");
    888             this._timelineGrid.element.addStyleClass("small");
    889             this._viewsContainerElement.addStyleClass("small");
    890         } else {
    891             this._largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
    892             this._dataGrid.element.removeStyleClass("small");
    893             this._timelineGrid.element.removeStyleClass("small");
    894             this._viewsContainerElement.removeStyleClass("small");
    895         }
    896         this._updateOffscreenRows();
    897     },
    898 
    899     _getPopoverAnchor: function(element)
    900     {
    901         var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label");
    902         if (!anchor)
    903             return null;
    904         var resource = anchor.parentElement.resource;
    905         return resource && resource.timing ? anchor : null;
    906     },
    907 
    908     _showPopover: function(anchor)
    909     {
    910         var resource = anchor.parentElement.resource;
    911         var tableElement = WebInspector.ResourceTimingView.createTimingTable(resource);
    912         var popover = new WebInspector.Popover(tableElement);
    913         popover.show(anchor);
    914         return popover;
    915     },
    916 
    917     _toggleGridMode: function()
    918     {
    919         if (this._viewingResourceMode) {
    920             this._viewingResourceMode = false;
    921             this.element.removeStyleClass("viewing-resource");
    922             this._dataGrid.element.removeStyleClass("viewing-resource-mode");
    923             this._viewsContainerElement.addStyleClass("hidden");
    924             this.sidebarElement.style.right = 0;
    925             this.sidebarElement.style.removeProperty("width");
    926             if (this._dataGrid.selectedNode)
    927                 this._dataGrid.selectedNode.selected = false;
    928         }
    929 
    930         if (this._briefGrid) {
    931             this._dataGrid.element.removeStyleClass("full-grid-mode");
    932             this._dataGrid.element.addStyleClass("brief-grid-mode");
    933 
    934             this._dataGrid.hideColumn("method");
    935             this._dataGrid.hideColumn("status");
    936             this._dataGrid.hideColumn("type");
    937             this._dataGrid.hideColumn("size");
    938             this._dataGrid.hideColumn("time");
    939 
    940             var widths = {};
    941             widths.name = 20;
    942             widths.timeline = 80;
    943         } else {
    944             this._dataGrid.element.addStyleClass("full-grid-mode");
    945             this._dataGrid.element.removeStyleClass("brief-grid-mode");
    946 
    947             this._dataGrid.showColumn("method");
    948             this._dataGrid.showColumn("status");
    949             this._dataGrid.showColumn("type");
    950             this._dataGrid.showColumn("size");
    951             this._dataGrid.showColumn("time");
    952 
    953             var widths = {};
    954             widths.name = 20;
    955             widths.method = 7;
    956             widths.status = 8;
    957             widths.type = 10;
    958             widths.size = 10;
    959             widths.time = 10;
    960             widths.timeline = 37;
    961         }
    962 
    963         this._dataGrid.showColumn("timeline");
    964         this._dataGrid.applyColumnWidthsMap(widths);
    965 
    966     },
    967 
    968     _toggleViewingResourceMode: function()
    969     {
    970         if (this._viewingResourceMode)
    971             return;
    972         this._viewingResourceMode = true;
    973         this._preservedColumnWidths = this._dataGrid.columnWidthsMap();
    974 
    975         this.element.addStyleClass("viewing-resource");
    976         this._dataGrid.element.addStyleClass("viewing-resource-mode");
    977         this._dataGrid.element.removeStyleClass("full-grid-mode");
    978         this._dataGrid.element.removeStyleClass("brief-grid-mode");
    979 
    980         this._dataGrid.hideColumn("method");
    981         this._dataGrid.hideColumn("status");
    982         this._dataGrid.hideColumn("type");
    983         this._dataGrid.hideColumn("size");
    984         this._dataGrid.hideColumn("time");
    985         this._dataGrid.hideColumn("timeline");
    986 
    987         this._viewsContainerElement.removeStyleClass("hidden");
    988         this.updateSidebarWidth(200);
    989 
    990         var widths = {};
    991         widths.name = 100;
    992         this._dataGrid.applyColumnWidthsMap(widths);
    993     },
    994 
    995     _contextMenu: function(event)
    996     {
    997         var contextMenu = new WebInspector.ContextMenu();
    998         var gridNode = this._dataGrid.dataGridNodeFromNode(event.target);
    999         var resource = gridNode && gridNode._resource;
   1000         if (resource)
   1001             contextMenu.appendItem(WebInspector.UIString("Copy entry as HAR"), this._exportResource.bind(this, resource));
   1002         contextMenu.appendItem(WebInspector.UIString("Copy network log as HAR"), this._exportAll.bind(this));
   1003         contextMenu.show(event);
   1004     },
   1005 
   1006     _exportAll: function()
   1007     {
   1008         var harArchive = {
   1009             log: (new WebInspector.HARLog()).build()
   1010         }
   1011         InspectorFrontendHost.copyText(JSON.stringify(harArchive));
   1012     },
   1013 
   1014     _exportResource: function(resource)
   1015     {
   1016         var har = (new WebInspector.HAREntry(resource)).build();
   1017         InspectorFrontendHost.copyText(JSON.stringify(har));
   1018     },
   1019 
   1020     _updateOffscreenRows: function(e)
   1021     {
   1022         var dataTableBody = this._dataGrid.dataTableBody;
   1023         var rows = dataTableBody.children;
   1024         var recordsCount = rows.length;
   1025         if (recordsCount < 2)
   1026             return;  // Filler row only.
   1027 
   1028         var visibleTop = this._dataGrid.scrollContainer.scrollTop;
   1029         var visibleBottom = visibleTop + this._dataGrid.scrollContainer.offsetHeight;
   1030 
   1031         var rowHeight = 0;
   1032 
   1033         // Filler is at recordsCount - 1.
   1034         var unfilteredRowIndex = 0;
   1035         for (var i = 0; i < recordsCount - 1; ++i) {
   1036             var row = rows[i];
   1037 
   1038             var dataGridNode = this._dataGrid.dataGridNodeFromNode(row);
   1039             if (dataGridNode.isFilteredOut()) {
   1040                 row.removeStyleClass("offscreen");
   1041                 continue;
   1042             }
   1043 
   1044             if (!rowHeight)
   1045                 rowHeight = row.offsetHeight;
   1046 
   1047             var rowIsVisible = unfilteredRowIndex * rowHeight < visibleBottom && (unfilteredRowIndex + 1) * rowHeight > visibleTop;
   1048             if (rowIsVisible !== row.rowIsVisible) {
   1049                 if (rowIsVisible)
   1050                     row.removeStyleClass("offscreen");
   1051                 else
   1052                     row.addStyleClass("offscreen");
   1053                 row.rowIsVisible = rowIsVisible;
   1054             }
   1055             unfilteredRowIndex++;
   1056         }
   1057     }
   1058 }
   1059 
   1060 WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype;
   1061 
   1062 WebInspector.NetworkBaseCalculator = function()
   1063 {
   1064 }
   1065 
   1066 WebInspector.NetworkBaseCalculator.prototype = {
   1067     computeSummaryValues: function(items)
   1068     {
   1069         var total = 0;
   1070         var categoryValues = {};
   1071 
   1072         var itemsLength = items.length;
   1073         for (var i = 0; i < itemsLength; ++i) {
   1074             var item = items[i];
   1075             var value = this._value(item);
   1076             if (typeof value === "undefined")
   1077                 continue;
   1078             if (!(item.category.name in categoryValues))
   1079                 categoryValues[item.category.name] = 0;
   1080             categoryValues[item.category.name] += value;
   1081             total += value;
   1082         }
   1083 
   1084         return {categoryValues: categoryValues, total: total};
   1085     },
   1086 
   1087     computeBarGraphPercentages: function(item)
   1088     {
   1089         return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100};
   1090     },
   1091 
   1092     computeBarGraphLabels: function(item)
   1093     {
   1094         const label = this.formatValue(this._value(item));
   1095         return {left: label, right: label, tooltip: label};
   1096     },
   1097 
   1098     get boundarySpan()
   1099     {
   1100         return this.maximumBoundary - this.minimumBoundary;
   1101     },
   1102 
   1103     updateBoundaries: function(item)
   1104     {
   1105         this.minimumBoundary = 0;
   1106 
   1107         var value = this._value(item);
   1108         if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
   1109             this.maximumBoundary = value;
   1110             return true;
   1111         }
   1112         return false;
   1113     },
   1114 
   1115     reset: function()
   1116     {
   1117         delete this.minimumBoundary;
   1118         delete this.maximumBoundary;
   1119     },
   1120 
   1121     _value: function(item)
   1122     {
   1123         return 0;
   1124     },
   1125 
   1126     formatValue: function(value)
   1127     {
   1128         return value.toString();
   1129     }
   1130 }
   1131 
   1132 WebInspector.NetworkTimeCalculator = function(startAtZero)
   1133 {
   1134     WebInspector.NetworkBaseCalculator.call(this);
   1135     this.startAtZero = startAtZero;
   1136 }
   1137 
   1138 WebInspector.NetworkTimeCalculator.prototype = {
   1139     computeSummaryValues: function(resources)
   1140     {
   1141         var resourcesByCategory = {};
   1142         var resourcesLength = resources.length;
   1143         for (var i = 0; i < resourcesLength; ++i) {
   1144             var resource = resources[i];
   1145             if (!(resource.category.name in resourcesByCategory))
   1146                 resourcesByCategory[resource.category.name] = [];
   1147             resourcesByCategory[resource.category.name].push(resource);
   1148         }
   1149 
   1150         var earliestStart;
   1151         var latestEnd;
   1152         var categoryValues = {};
   1153         for (var category in resourcesByCategory) {
   1154             resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
   1155             categoryValues[category] = 0;
   1156 
   1157             var segment = {start: -1, end: -1};
   1158 
   1159             var categoryResources = resourcesByCategory[category];
   1160             var resourcesLength = categoryResources.length;
   1161             for (var i = 0; i < resourcesLength; ++i) {
   1162                 var resource = categoryResources[i];
   1163                 if (resource.startTime === -1 || resource.endTime === -1)
   1164                     continue;
   1165 
   1166                 if (typeof earliestStart === "undefined")
   1167                     earliestStart = resource.startTime;
   1168                 else
   1169                     earliestStart = Math.min(earliestStart, resource.startTime);
   1170 
   1171                 if (typeof latestEnd === "undefined")
   1172                     latestEnd = resource.endTime;
   1173                 else
   1174                     latestEnd = Math.max(latestEnd, resource.endTime);
   1175 
   1176                 if (resource.startTime <= segment.end) {
   1177                     segment.end = Math.max(segment.end, resource.endTime);
   1178                     continue;
   1179                 }
   1180 
   1181                 categoryValues[category] += segment.end - segment.start;
   1182 
   1183                 segment.start = resource.startTime;
   1184                 segment.end = resource.endTime;
   1185             }
   1186 
   1187             // Add the last segment
   1188             categoryValues[category] += segment.end - segment.start;
   1189         }
   1190 
   1191         return {categoryValues: categoryValues, total: latestEnd - earliestStart};
   1192     },
   1193 
   1194     computeBarGraphPercentages: function(resource)
   1195     {
   1196         if (resource.startTime !== -1)
   1197             var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
   1198         else
   1199             var start = 0;
   1200 
   1201         if (resource.responseReceivedTime !== -1)
   1202             var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
   1203         else
   1204             var middle = (this.startAtZero ? start : 100);
   1205 
   1206         if (resource.endTime !== -1)
   1207             var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
   1208         else
   1209             var end = (this.startAtZero ? middle : 100);
   1210 
   1211         if (this.startAtZero) {
   1212             end -= start;
   1213             middle -= start;
   1214             start = 0;
   1215         }
   1216 
   1217         return {start: start, middle: middle, end: end};
   1218     },
   1219 
   1220     computePercentageFromEventTime: function(eventTime)
   1221     {
   1222         // This function computes a percentage in terms of the total loading time
   1223         // of a specific event. If startAtZero is set, then this is useless, and we
   1224         // want to return 0.
   1225         if (eventTime !== -1 && !this.startAtZero)
   1226             return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;
   1227 
   1228         return 0;
   1229     },
   1230 
   1231     computeBarGraphLabels: function(resource)
   1232     {
   1233         var rightLabel = "";
   1234         if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
   1235             rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
   1236 
   1237         var hasLatency = resource.latency > 0;
   1238         if (hasLatency)
   1239             var leftLabel = this.formatValue(resource.latency);
   1240         else
   1241             var leftLabel = rightLabel;
   1242 
   1243         if (resource.timing)
   1244             return {left: leftLabel, right: rightLabel};
   1245 
   1246         if (hasLatency && rightLabel) {
   1247             var total = this.formatValue(resource.duration);
   1248             var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
   1249         } else if (hasLatency)
   1250             var tooltip = WebInspector.UIString("%s latency", leftLabel);
   1251         else if (rightLabel)
   1252             var tooltip = WebInspector.UIString("%s download", rightLabel);
   1253 
   1254         if (resource.cached)
   1255             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
   1256         return {left: leftLabel, right: rightLabel, tooltip: tooltip};
   1257     },
   1258 
   1259     updateBoundaries: function(resource)
   1260     {
   1261         var didChange = false;
   1262 
   1263         var lowerBound;
   1264         if (this.startAtZero)
   1265             lowerBound = 0;
   1266         else
   1267             lowerBound = this._lowerBound(resource);
   1268 
   1269         if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
   1270             this.minimumBoundary = lowerBound;
   1271             didChange = true;
   1272         }
   1273 
   1274         var upperBound = this._upperBound(resource);
   1275         if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
   1276             this.maximumBoundary = upperBound;
   1277             didChange = true;
   1278         }
   1279 
   1280         return didChange;
   1281     },
   1282 
   1283     formatValue: function(value)
   1284     {
   1285         return Number.secondsToString(value);
   1286     },
   1287 
   1288     _lowerBound: function(resource)
   1289     {
   1290         return 0;
   1291     },
   1292 
   1293     _upperBound: function(resource)
   1294     {
   1295         return 0;
   1296     }
   1297 }
   1298 
   1299 WebInspector.NetworkTimeCalculator.prototype.__proto__ = WebInspector.NetworkBaseCalculator.prototype;
   1300 
   1301 WebInspector.NetworkTransferTimeCalculator = function()
   1302 {
   1303     WebInspector.NetworkTimeCalculator.call(this, false);
   1304 }
   1305 
   1306 WebInspector.NetworkTransferTimeCalculator.prototype = {
   1307     formatValue: function(value)
   1308     {
   1309         return Number.secondsToString(value);
   1310     },
   1311 
   1312     _lowerBound: function(resource)
   1313     {
   1314         return resource.startTime;
   1315     },
   1316 
   1317     _upperBound: function(resource)
   1318     {
   1319         return resource.endTime;
   1320     }
   1321 }
   1322 
   1323 WebInspector.NetworkTransferTimeCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
   1324 
   1325 WebInspector.NetworkTransferDurationCalculator = function()
   1326 {
   1327     WebInspector.NetworkTimeCalculator.call(this, true);
   1328 }
   1329 
   1330 WebInspector.NetworkTransferDurationCalculator.prototype = {
   1331     formatValue: function(value)
   1332     {
   1333         return Number.secondsToString(value);
   1334     },
   1335 
   1336     _upperBound: function(resource)
   1337     {
   1338         return resource.duration;
   1339     }
   1340 }
   1341 
   1342 WebInspector.NetworkTransferDurationCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
   1343 
   1344 WebInspector.NetworkDataGridNode = function(panel, resource)
   1345 {
   1346     WebInspector.DataGridNode.call(this, {});
   1347     this._panel = panel;
   1348     this._resource = resource;
   1349 }
   1350 
   1351 WebInspector.NetworkDataGridNode.prototype = {
   1352     createCells: function()
   1353     {
   1354         this._nameCell = this._createDivInTD("name");
   1355         this._methodCell = this._createDivInTD("method");
   1356         this._statusCell = this._createDivInTD("status");
   1357         this._typeCell = this._createDivInTD("type");
   1358         this._sizeCell = this._createDivInTD("size");
   1359         this._timeCell = this._createDivInTD("time");
   1360         this._createTimelineCell();
   1361         this._nameCell.addEventListener("click", this.select.bind(this), false);
   1362         this._nameCell.addEventListener("dblclick", this._openInNewTab.bind(this), false);
   1363     },
   1364 
   1365     isFilteredOut: function()
   1366     {
   1367         if (!this._panel._hiddenCategories.all)
   1368             return false;
   1369         return this._resource.category.name in this._panel._hiddenCategories;
   1370     },
   1371 
   1372     select: function()
   1373     {
   1374         this._panel._showResource(this._resource);
   1375         WebInspector.DataGridNode.prototype.select.apply(this, arguments);
   1376     },
   1377 
   1378     _openInNewTab: function()
   1379     {
   1380         PageAgent.openInInspectedWindow(this._resource.url);
   1381     },
   1382 
   1383     get selectable()
   1384     {
   1385         if (!this._panel._viewingResourceMode)
   1386             return false;
   1387         return !this.isFilteredOut();
   1388     },
   1389 
   1390     _createDivInTD: function(columnIdentifier)
   1391     {
   1392         var td = document.createElement("td");
   1393         td.className = columnIdentifier + "-column";
   1394         var div = document.createElement("div");
   1395         td.appendChild(div);
   1396         this._element.appendChild(td);
   1397         return div;
   1398     },
   1399 
   1400     _createTimelineCell: function()
   1401     {
   1402         this._graphElement = document.createElement("div");
   1403         this._graphElement.className = "network-graph-side";
   1404 
   1405         this._barAreaElement = document.createElement("div");
   1406         //    this._barAreaElement.className = "network-graph-bar-area hidden";
   1407         this._barAreaElement.className = "network-graph-bar-area";
   1408         this._barAreaElement.resource = this._resource;
   1409         this._graphElement.appendChild(this._barAreaElement);
   1410 
   1411         this._barLeftElement = document.createElement("div");
   1412         this._barLeftElement.className = "network-graph-bar waiting";
   1413         this._barAreaElement.appendChild(this._barLeftElement);
   1414 
   1415         this._barRightElement = document.createElement("div");
   1416         this._barRightElement.className = "network-graph-bar";
   1417         this._barAreaElement.appendChild(this._barRightElement);
   1418 
   1419 
   1420         this._labelLeftElement = document.createElement("div");
   1421         this._labelLeftElement.className = "network-graph-label waiting";
   1422         this._barAreaElement.appendChild(this._labelLeftElement);
   1423 
   1424         this._labelRightElement = document.createElement("div");
   1425         this._labelRightElement.className = "network-graph-label";
   1426         this._barAreaElement.appendChild(this._labelRightElement);
   1427 
   1428         this._graphElement.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false);
   1429 
   1430         this._timelineCell = document.createElement("td");
   1431         this._timelineCell.className = "timeline-column";
   1432         this._element.appendChild(this._timelineCell);
   1433         this._timelineCell.appendChild(this._graphElement);
   1434     },
   1435 
   1436     refreshResource: function()
   1437     {
   1438         this._refreshNameCell();
   1439 
   1440         this._methodCell.textContent = this._resource.requestMethod;
   1441 
   1442         this._refreshStatusCell();
   1443 
   1444         if (this._resource.mimeType) {
   1445             this._typeCell.removeStyleClass("network-dim-cell");
   1446             this._typeCell.textContent = this._resource.mimeType;
   1447         } else {
   1448             this._typeCell.addStyleClass("network-dim-cell");
   1449             this._typeCell.textContent = WebInspector.UIString("Pending");
   1450         }
   1451 
   1452         this._refreshSizeCell();
   1453         this._refreshTimeCell();
   1454 
   1455         if (this._resource.cached)
   1456             this._graphElement.addStyleClass("resource-cached");
   1457 
   1458         this._element.addStyleClass("network-item");
   1459         if (!this._element.hasStyleClass("network-category-" + this._resource.category.name)) {
   1460             this._element.removeMatchingStyleClasses("network-category-\\w+");
   1461             this._element.addStyleClass("network-category-" + this._resource.category.name);
   1462         }
   1463     },
   1464 
   1465     _refreshNameCell: function()
   1466     {
   1467         this._nameCell.removeChildren();
   1468 
   1469         if (this._resource.category === WebInspector.resourceCategories.images) {
   1470             var previewImage = document.createElement("img");
   1471             previewImage.className = "image-network-icon-preview";
   1472             this._resource.populateImageSource(previewImage);
   1473 
   1474             var iconElement = document.createElement("div");
   1475             iconElement.className = "icon";
   1476             iconElement.appendChild(previewImage);
   1477         } else {
   1478             var iconElement = document.createElement("img");
   1479             iconElement.className = "icon";
   1480         }
   1481         this._nameCell.appendChild(iconElement);
   1482         this._nameCell.appendChild(document.createTextNode(this._fileName()));
   1483 
   1484 
   1485         var subtitle = this._resource.displayDomain;
   1486 
   1487         if (this._resource.path && this._resource.lastPathComponent) {
   1488             var lastPathComponentIndex = this._resource.path.lastIndexOf("/" + this._resource.lastPathComponent);
   1489             if (lastPathComponentIndex != -1)
   1490                 subtitle += this._resource.path.substring(0, lastPathComponentIndex);
   1491         }
   1492 
   1493         this._appendSubtitle(this._nameCell, subtitle);
   1494         this._nameCell.title = this._resource.url;
   1495     },
   1496 
   1497     _fileName: function()
   1498     {
   1499         var fileName = this._resource.displayName;
   1500         if (this._resource.queryString)
   1501             fileName += "?" + this._resource.queryString;
   1502         return fileName;
   1503     },
   1504 
   1505     _refreshStatusCell: function()
   1506     {
   1507         this._statusCell.removeChildren();
   1508 
   1509         if (this._resource.failed) {
   1510             if (this._resource.canceled)
   1511                 this._statusCell.textContent = WebInspector.UIString("(canceled)");
   1512             else
   1513                 this._statusCell.textContent = WebInspector.UIString("(failed)");
   1514             this._statusCell.addStyleClass("network-dim-cell");
   1515             return;
   1516         }
   1517 
   1518         var fromCache = this._resource.cached;
   1519         if (fromCache) {
   1520             this._statusCell.textContent = WebInspector.UIString("(from cache)");
   1521             this._statusCell.addStyleClass("network-dim-cell");
   1522             return;
   1523         }
   1524 
   1525         this._statusCell.removeStyleClass("network-dim-cell");
   1526         if (this._resource.statusCode) {
   1527             this._statusCell.appendChild(document.createTextNode(this._resource.statusCode));
   1528             this._statusCell.removeStyleClass("network-dim-cell");
   1529             this._appendSubtitle(this._statusCell, this._resource.statusText);
   1530             this._statusCell.title = this._resource.statusCode + " " + this._resource.statusText;
   1531         } else {
   1532             if (this._resource.isDataURL() && this._resource.finished)
   1533                 this._statusCell.textContent = WebInspector.UIString("(data url)");
   1534             else
   1535                 this._statusCell.textContent = WebInspector.UIString("Pending");
   1536             this._statusCell.addStyleClass("network-dim-cell");
   1537         }
   1538     },
   1539 
   1540     _refreshSizeCell: function()
   1541     {
   1542         var resourceSize = typeof this._resource.resourceSize === "number" ? Number.bytesToString(this._resource.resourceSize) : "?";
   1543         var transferSize = typeof this._resource.transferSize === "number" ? Number.bytesToString(this._resource.transferSize) : "?";
   1544         var fromCache = this._resource.cached;
   1545         this._sizeCell.textContent = !fromCache ? resourceSize : WebInspector.UIString("(from cache)");
   1546         if (fromCache)
   1547             this._sizeCell.addStyleClass("network-dim-cell");
   1548         else
   1549             this._sizeCell.removeStyleClass("network-dim-cell");
   1550         if (!fromCache)
   1551             this._appendSubtitle(this._sizeCell, transferSize);
   1552     },
   1553 
   1554     _refreshTimeCell: function()
   1555     {
   1556         if (this._resource.duration > 0) {
   1557             this._timeCell.removeStyleClass("network-dim-cell");
   1558             this._timeCell.textContent = Number.secondsToString(this._resource.duration);
   1559             this._appendSubtitle(this._timeCell, Number.secondsToString(this._resource.latency));
   1560         } else {
   1561             this._timeCell.addStyleClass("network-dim-cell");
   1562             this._timeCell.textContent = WebInspector.UIString("Pending");
   1563         }
   1564     },
   1565 
   1566     _appendSubtitle: function(cellElement, subtitleText)
   1567     {
   1568         var subtitleElement = document.createElement("div");
   1569         subtitleElement.className = "network-cell-subtitle";
   1570         subtitleElement.textContent = subtitleText;
   1571         cellElement.appendChild(subtitleElement);
   1572     },
   1573 
   1574     refreshGraph: function(calculator)
   1575     {
   1576         var percentages = calculator.computeBarGraphPercentages(this._resource);
   1577         this._percentages = percentages;
   1578 
   1579         this._barAreaElement.removeStyleClass("hidden");
   1580 
   1581         if (!this._graphElement.hasStyleClass("network-category-" + this._resource.category.name)) {
   1582             this._graphElement.removeMatchingStyleClasses("network-category-\\w+");
   1583             this._graphElement.addStyleClass("network-category-" + this._resource.category.name);
   1584         }
   1585 
   1586         this._barLeftElement.style.setProperty("left", percentages.start + "%");
   1587         this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
   1588 
   1589         this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
   1590         this._barRightElement.style.setProperty("left", percentages.middle + "%");
   1591 
   1592         var labels = calculator.computeBarGraphLabels(this._resource);
   1593         this._labelLeftElement.textContent = labels.left;
   1594         this._labelRightElement.textContent = labels.right;
   1595 
   1596         var tooltip = (labels.tooltip || "");
   1597         this._barLeftElement.title = tooltip;
   1598         this._labelLeftElement.title = tooltip;
   1599         this._labelRightElement.title = tooltip;
   1600         this._barRightElement.title = tooltip;
   1601     },
   1602 
   1603     _refreshLabelPositions: function()
   1604     {
   1605         if (!this._percentages)
   1606             return;
   1607         this._labelLeftElement.style.removeProperty("left");
   1608         this._labelLeftElement.style.removeProperty("right");
   1609         this._labelLeftElement.removeStyleClass("before");
   1610         this._labelLeftElement.removeStyleClass("hidden");
   1611 
   1612         this._labelRightElement.style.removeProperty("left");
   1613         this._labelRightElement.style.removeProperty("right");
   1614         this._labelRightElement.removeStyleClass("after");
   1615         this._labelRightElement.removeStyleClass("hidden");
   1616 
   1617         const labelPadding = 10;
   1618         const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
   1619         const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;
   1620 
   1621         if (this._barLeftElement) {
   1622             var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
   1623             var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
   1624         } else {
   1625             var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
   1626             var rightBarWidth = barRightElementOffsetWidth - labelPadding;
   1627         }
   1628 
   1629         const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
   1630         const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;
   1631 
   1632         const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
   1633         const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
   1634         const graphElementOffsetWidth = this._graphElement.offsetWidth;
   1635 
   1636         if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
   1637             var leftHidden = true;
   1638 
   1639         if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
   1640             var rightHidden = true;
   1641 
   1642         if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
   1643             // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
   1644             if (labelBefore && !labelAfter)
   1645                 leftHidden = true;
   1646             else if (labelAfter && !labelBefore)
   1647                 rightHidden = true;
   1648         }
   1649 
   1650         if (labelBefore) {
   1651             if (leftHidden)
   1652                 this._labelLeftElement.addStyleClass("hidden");
   1653             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
   1654             this._labelLeftElement.addStyleClass("before");
   1655         } else {
   1656             this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
   1657             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
   1658         }
   1659 
   1660         if (labelAfter) {
   1661             if (rightHidden)
   1662                 this._labelRightElement.addStyleClass("hidden");
   1663             this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
   1664             this._labelRightElement.addStyleClass("after");
   1665         } else {
   1666             this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
   1667             this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
   1668         }
   1669     }
   1670 }
   1671 
   1672 WebInspector.NetworkDataGridNode.NameComparator = function(a, b)
   1673 {
   1674     var aFileName = a._resource.displayName + (a._resource.queryString ? a._resource.queryString : "");
   1675     var bFileName = b._resource.displayName + (b._resource.queryString ? b._resource.queryString : "");
   1676     if (aFileName > bFileName)
   1677         return 1;
   1678     if (bFileName > aFileName)
   1679         return -1;
   1680     return 0;
   1681 }
   1682 
   1683 WebInspector.NetworkDataGridNode.SizeComparator = function(a, b)
   1684 {
   1685     if (b._resource.cached && !a._resource.cached)
   1686         return 1;
   1687     if (a._resource.cached && !b._resource.cached)
   1688         return -1;
   1689 
   1690     if (a._resource.resourceSize === b._resource.resourceSize)
   1691         return 0;
   1692 
   1693     return a._resource.resourceSize - b._resource.resourceSize;
   1694 }
   1695 
   1696 WebInspector.NetworkDataGridNode.ResourcePropertyComparator = function(propertyName, revert, a, b)
   1697 {
   1698     var aValue = a._resource[propertyName];
   1699     var bValue = b._resource[propertyName];
   1700     if (aValue > bValue)
   1701         return revert ? -1 : 1;
   1702     if (bValue > aValue)
   1703         return revert ? 1 : -1;
   1704     return 0;
   1705 }
   1706 
   1707 WebInspector.NetworkDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
   1708