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