2011-01-14 Andrey Kosyakov <caseq@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     mainResourceChanged: function()
808     {
809         if (this._preserveLogToggle.toggled)
810             return;
811
812         this._reset();
813         // Now resurrect the main resource along with all redirects that lead to it.
814         var resourcesToAppend = (WebInspector.mainResource.redirects || []).concat(WebInspector.mainResource);
815         resourcesToAppend.forEach(this.appendResource, this);
816     },
817
818     canShowSourceLine: function(url, line)
819     {
820         return !!this._resourcesByURL[url];
821     },
822
823     showSourceLine: function(url, line)
824     {
825         this._showResource(this._resourcesByURL[url], line);
826     },
827
828     _showResource: function(resource, line)
829     {
830         if (!resource)
831             return;
832
833         this._popoverHelper.hidePopup();
834
835         this._toggleViewingResourceMode();
836
837         if (this.visibleView) {
838             this.visibleView.detach();
839             delete this.visibleView;
840         }
841
842         var view = new WebInspector.NetworkItemView(resource);
843         view.show(this._viewsContainerElement);
844         this.visibleView = view;
845
846         this.updateSidebarWidth();
847     },
848
849     _closeVisibleResource: function()
850     {
851         this.element.removeStyleClass("viewing-resource");
852
853         if (this.visibleView) {
854             this.visibleView.detach();
855             delete this.visibleView;
856         }
857
858         if (this._lastSelectedGraphTreeElement)
859             this._lastSelectedGraphTreeElement.select(true);
860
861         this.updateSidebarWidth();
862     },
863
864     _toggleLargerResources: function()
865     {
866         WebInspector.settings.resourcesLargeRows = !WebInspector.settings.resourcesLargeRows;
867         this._setLargerResources(WebInspector.settings.resourcesLargeRows);
868     },
869
870     _setLargerResources: function(enabled)
871     {
872         this._largerResourcesButton.toggled = enabled;
873         if (!enabled) {
874             this._largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
875             this._dataGrid.element.addStyleClass("small");
876             this._timelineGrid.element.addStyleClass("small");
877             this._viewsContainerElement.addStyleClass("small");
878         } else {
879             this._largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
880             this._dataGrid.element.removeStyleClass("small");
881             this._timelineGrid.element.removeStyleClass("small");
882             this._viewsContainerElement.removeStyleClass("small");
883         }
884         this._positionSummaryBar();
885     },
886
887     _getPopoverAnchor: function(element)
888     {
889         var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label");
890         if (!anchor)
891             return null;
892         var resource = anchor.parentElement.resource;
893         return resource && resource.timing ? anchor : null;
894     },
895
896     _showPopover: function(anchor)
897     {
898         var resource = anchor.parentElement.resource;
899         var tableElement = WebInspector.ResourceTimingView.createTimingTable(resource);
900         var popover = new WebInspector.Popover(tableElement);
901         popover.show(anchor);
902         return popover;
903     },
904
905     _toggleGridMode: function()
906     {
907         if (this._viewingResourceMode) {
908             this._viewingResourceMode = false;
909             this.element.removeStyleClass("viewing-resource");
910             this._dataGrid.element.removeStyleClass("viewing-resource-mode");
911             this._viewsContainerElement.addStyleClass("hidden");
912             this.sidebarElement.style.right = 0;
913             this.sidebarElement.style.removeProperty("width");
914             this._summaryBarElement.style.removeProperty("width");
915             if (this._dataGrid.selectedNode)
916                 this._dataGrid.selectedNode.selected = false;
917         }
918
919         if (this._briefGrid) {
920             this._dataGrid.element.removeStyleClass("full-grid-mode");
921             this._dataGrid.element.addStyleClass("brief-grid-mode");
922
923             this._dataGrid.hideColumn("method");
924             this._dataGrid.hideColumn("status");
925             this._dataGrid.hideColumn("type");
926             this._dataGrid.hideColumn("size");
927             this._dataGrid.hideColumn("time");
928
929             var widths = {};
930             widths.name = 20;
931             widths.timeline = 80;
932         } else {
933             this._dataGrid.element.addStyleClass("full-grid-mode");
934             this._dataGrid.element.removeStyleClass("brief-grid-mode");
935
936             this._dataGrid.showColumn("method");
937             this._dataGrid.showColumn("status");
938             this._dataGrid.showColumn("type");
939             this._dataGrid.showColumn("size");
940             this._dataGrid.showColumn("time");
941
942             var widths = {};
943             widths.name = 20;
944             widths.method = 7;
945             widths.status = 8;
946             widths.type = 10;
947             widths.size = 10;
948             widths.time = 10;
949             widths.timeline = 37;
950         }
951
952         this._dataGrid.showColumn("timeline");
953         this._dataGrid.applyColumnWidthsMap(widths);
954
955     },
956
957     _toggleViewingResourceMode: function()
958     {
959         if (this._viewingResourceMode)
960             return;
961         this._viewingResourceMode = true;
962         this._preservedColumnWidths = this._dataGrid.columnWidthsMap();
963
964         this.element.addStyleClass("viewing-resource");
965         this._dataGrid.element.addStyleClass("viewing-resource-mode");
966         this._dataGrid.element.removeStyleClass("full-grid-mode");
967         this._dataGrid.element.removeStyleClass("brief-grid-mode");
968
969         this._dataGrid.hideColumn("method");
970         this._dataGrid.hideColumn("status");
971         this._dataGrid.hideColumn("type");
972         this._dataGrid.hideColumn("size");
973         this._dataGrid.hideColumn("time");
974         this._dataGrid.hideColumn("timeline");
975
976         this._viewsContainerElement.removeStyleClass("hidden");
977         this.updateSidebarWidth(200);
978
979         var widths = {};
980         widths.name = 100;
981         this._dataGrid.applyColumnWidthsMap(widths);
982     },
983
984     _contextMenu: function(event)
985     {
986         // createBlobURL is enabled conditionally, do not expose resource export if it's not available.
987         if (typeof window.webkitURL.createObjectURL !== "function" || !Preferences.resourceExportEnabled)
988             return;
989
990         var contextMenu = new WebInspector.ContextMenu();
991         var gridNode = this._dataGrid.dataGridNodeFromNode(event.target);
992         var resource = gridNode && gridNode._resource;
993         if (resource)
994             contextMenu.appendItem(WebInspector.UIString("Export to HAR"), this._exportResource.bind(this, resource));
995         contextMenu.appendItem(WebInspector.UIString("Export all to HAR"), this._exportAll.bind(this));
996         contextMenu.show(event);
997     },
998
999     _exportAll: function()
1000     {
1001         var harArchive = {
1002             log: (new WebInspector.HARLog()).build()
1003         }
1004         offerFileForDownload(JSON.stringify(harArchive));
1005     },
1006
1007     _exportResource: function(resource)
1008     {
1009         var har = (new WebInspector.HAREntry(resource)).build();
1010         offerFileForDownload(JSON.stringify(har));
1011     }
1012 }
1013
1014 WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype;
1015
1016 WebInspector.NetworkBaseCalculator = function()
1017 {
1018 }
1019
1020 WebInspector.NetworkBaseCalculator.prototype = {
1021     computeSummaryValues: function(items)
1022     {
1023         var total = 0;
1024         var categoryValues = {};
1025
1026         var itemsLength = items.length;
1027         for (var i = 0; i < itemsLength; ++i) {
1028             var item = items[i];
1029             var value = this._value(item);
1030             if (typeof value === "undefined")
1031                 continue;
1032             if (!(item.category.name in categoryValues))
1033                 categoryValues[item.category.name] = 0;
1034             categoryValues[item.category.name] += value;
1035             total += value;
1036         }
1037
1038         return {categoryValues: categoryValues, total: total};
1039     },
1040
1041     computeBarGraphPercentages: function(item)
1042     {
1043         return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100};
1044     },
1045
1046     computeBarGraphLabels: function(item)
1047     {
1048         const label = this.formatValue(this._value(item));
1049         return {left: label, right: label, tooltip: label};
1050     },
1051
1052     get boundarySpan()
1053     {
1054         return this.maximumBoundary - this.minimumBoundary;
1055     },
1056
1057     updateBoundaries: function(item)
1058     {
1059         this.minimumBoundary = 0;
1060
1061         var value = this._value(item);
1062         if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
1063             this.maximumBoundary = value;
1064             return true;
1065         }
1066         return false;
1067     },
1068
1069     reset: function()
1070     {
1071         delete this.minimumBoundary;
1072         delete this.maximumBoundary;
1073     },
1074
1075     _value: function(item)
1076     {
1077         return 0;
1078     },
1079
1080     formatValue: function(value)
1081     {
1082         return value.toString();
1083     }
1084 }
1085
1086 WebInspector.NetworkTimeCalculator = function(startAtZero)
1087 {
1088     WebInspector.NetworkBaseCalculator.call(this);
1089     this.startAtZero = startAtZero;
1090 }
1091
1092 WebInspector.NetworkTimeCalculator.prototype = {
1093     computeSummaryValues: function(resources)
1094     {
1095         var resourcesByCategory = {};
1096         var resourcesLength = resources.length;
1097         for (var i = 0; i < resourcesLength; ++i) {
1098             var resource = resources[i];
1099             if (!(resource.category.name in resourcesByCategory))
1100                 resourcesByCategory[resource.category.name] = [];
1101             resourcesByCategory[resource.category.name].push(resource);
1102         }
1103
1104         var earliestStart;
1105         var latestEnd;
1106         var categoryValues = {};
1107         for (var category in resourcesByCategory) {
1108             resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
1109             categoryValues[category] = 0;
1110
1111             var segment = {start: -1, end: -1};
1112
1113             var categoryResources = resourcesByCategory[category];
1114             var resourcesLength = categoryResources.length;
1115             for (var i = 0; i < resourcesLength; ++i) {
1116                 var resource = categoryResources[i];
1117                 if (resource.startTime === -1 || resource.endTime === -1)
1118                     continue;
1119
1120                 if (typeof earliestStart === "undefined")
1121                     earliestStart = resource.startTime;
1122                 else
1123                     earliestStart = Math.min(earliestStart, resource.startTime);
1124
1125                 if (typeof latestEnd === "undefined")
1126                     latestEnd = resource.endTime;
1127                 else
1128                     latestEnd = Math.max(latestEnd, resource.endTime);
1129
1130                 if (resource.startTime <= segment.end) {
1131                     segment.end = Math.max(segment.end, resource.endTime);
1132                     continue;
1133                 }
1134
1135                 categoryValues[category] += segment.end - segment.start;
1136
1137                 segment.start = resource.startTime;
1138                 segment.end = resource.endTime;
1139             }
1140
1141             // Add the last segment
1142             categoryValues[category] += segment.end - segment.start;
1143         }
1144
1145         return {categoryValues: categoryValues, total: latestEnd - earliestStart};
1146     },
1147
1148     computeBarGraphPercentages: function(resource)
1149     {
1150         if (resource.startTime !== -1)
1151             var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
1152         else
1153             var start = 0;
1154
1155         if (resource.responseReceivedTime !== -1)
1156             var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
1157         else
1158             var middle = (this.startAtZero ? start : 100);
1159
1160         if (resource.endTime !== -1)
1161             var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
1162         else
1163             var end = (this.startAtZero ? middle : 100);
1164
1165         if (this.startAtZero) {
1166             end -= start;
1167             middle -= start;
1168             start = 0;
1169         }
1170
1171         return {start: start, middle: middle, end: end};
1172     },
1173
1174     computePercentageFromEventTime: function(eventTime)
1175     {
1176         // This function computes a percentage in terms of the total loading time
1177         // of a specific event. If startAtZero is set, then this is useless, and we
1178         // want to return 0.
1179         if (eventTime !== -1 && !this.startAtZero)
1180             return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;
1181
1182         return 0;
1183     },
1184
1185     computeBarGraphLabels: function(resource)
1186     {
1187         var rightLabel = "";
1188         if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
1189             rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
1190
1191         var hasLatency = resource.latency > 0;
1192         if (hasLatency)
1193             var leftLabel = this.formatValue(resource.latency);
1194         else
1195             var leftLabel = rightLabel;
1196
1197         if (resource.timing)
1198             return {left: leftLabel, right: rightLabel};
1199
1200         if (hasLatency && rightLabel) {
1201             var total = this.formatValue(resource.duration);
1202             var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
1203         } else if (hasLatency)
1204             var tooltip = WebInspector.UIString("%s latency", leftLabel);
1205         else if (rightLabel)
1206             var tooltip = WebInspector.UIString("%s download", rightLabel);
1207
1208         if (resource.cached)
1209             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1210         return {left: leftLabel, right: rightLabel, tooltip: tooltip};
1211     },
1212
1213     updateBoundaries: function(resource)
1214     {
1215         var didChange = false;
1216
1217         var lowerBound;
1218         if (this.startAtZero)
1219             lowerBound = 0;
1220         else
1221             lowerBound = this._lowerBound(resource);
1222
1223         if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
1224             this.minimumBoundary = lowerBound;
1225             didChange = true;
1226         }
1227
1228         var upperBound = this._upperBound(resource);
1229         if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
1230             this.maximumBoundary = upperBound;
1231             didChange = true;
1232         }
1233
1234         return didChange;
1235     },
1236
1237     formatValue: function(value)
1238     {
1239         return Number.secondsToString(value);
1240     },
1241
1242     _lowerBound: function(resource)
1243     {
1244         return 0;
1245     },
1246
1247     _upperBound: function(resource)
1248     {
1249         return 0;
1250     }
1251 }
1252
1253 WebInspector.NetworkTimeCalculator.prototype.__proto__ = WebInspector.NetworkBaseCalculator.prototype;
1254
1255 WebInspector.NetworkTransferTimeCalculator = function()
1256 {
1257     WebInspector.NetworkTimeCalculator.call(this, false);
1258 }
1259
1260 WebInspector.NetworkTransferTimeCalculator.prototype = {
1261     formatValue: function(value)
1262     {
1263         return Number.secondsToString(value);
1264     },
1265
1266     _lowerBound: function(resource)
1267     {
1268         return resource.startTime;
1269     },
1270
1271     _upperBound: function(resource)
1272     {
1273         return resource.endTime;
1274     }
1275 }
1276
1277 WebInspector.NetworkTransferTimeCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
1278
1279 WebInspector.NetworkTransferDurationCalculator = function()
1280 {
1281     WebInspector.NetworkTimeCalculator.call(this, true);
1282 }
1283
1284 WebInspector.NetworkTransferDurationCalculator.prototype = {
1285     formatValue: function(value)
1286     {
1287         return Number.secondsToString(value);
1288     },
1289
1290     _upperBound: function(resource)
1291     {
1292         return resource.duration;
1293     }
1294 }
1295
1296 WebInspector.NetworkTransferDurationCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
1297
1298 WebInspector.NetworkDataGridNode = function(panel, resource)
1299 {
1300     WebInspector.DataGridNode.call(this, {});
1301     this._panel = panel;
1302     this._resource = resource;
1303 }
1304
1305 WebInspector.NetworkDataGridNode.prototype = {
1306     createCells: function()
1307     {
1308         this._nameCell = this._createDivInTD("name");
1309         this._methodCell = this._createDivInTD("method");
1310         this._statusCell = this._createDivInTD("status");
1311         this._typeCell = this._createDivInTD("type");
1312         this._sizeCell = this._createDivInTD("size");
1313         this._timeCell = this._createDivInTD("time");
1314         this._createTimelineCell();
1315         this._nameCell.addEventListener("click", this.select.bind(this), false);
1316     },
1317
1318     select: function()
1319     {
1320         this._panel._showResource(this._resource);
1321         WebInspector.DataGridNode.prototype.select.apply(this, arguments);
1322     },
1323
1324     get selectable()
1325     {
1326         if (!this._panel._viewingResourceMode)
1327             return false;
1328         if (!this._panel._hiddenCategories.all)
1329             return true;
1330         if (this._panel._hiddenCategories[this._resource.category.name])
1331             return false;
1332         return true;
1333     },
1334
1335     _createDivInTD: function(columnIdentifier)
1336     {
1337         var td = document.createElement("td");
1338         td.className = columnIdentifier + "-column";
1339         var div = document.createElement("div");
1340         td.appendChild(div);
1341         this._element.appendChild(td);
1342         return div;
1343     },
1344
1345     _createTimelineCell: function()
1346     {
1347         this._graphElement = document.createElement("div");
1348         this._graphElement.className = "network-graph-side";
1349
1350         this._barAreaElement = document.createElement("div");
1351         //    this._barAreaElement.className = "network-graph-bar-area hidden";
1352         this._barAreaElement.className = "network-graph-bar-area";
1353         this._barAreaElement.resource = this._resource;
1354         this._graphElement.appendChild(this._barAreaElement);
1355
1356         this._barLeftElement = document.createElement("div");
1357         this._barLeftElement.className = "network-graph-bar waiting";
1358         this._barAreaElement.appendChild(this._barLeftElement);
1359
1360         this._barRightElement = document.createElement("div");
1361         this._barRightElement.className = "network-graph-bar";
1362         this._barAreaElement.appendChild(this._barRightElement);
1363
1364
1365         this._labelLeftElement = document.createElement("div");
1366         this._labelLeftElement.className = "network-graph-label waiting";
1367         this._barAreaElement.appendChild(this._labelLeftElement);
1368
1369         this._labelRightElement = document.createElement("div");
1370         this._labelRightElement.className = "network-graph-label";
1371         this._barAreaElement.appendChild(this._labelRightElement);
1372
1373         this._graphElement.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false);
1374
1375         this._timelineCell = document.createElement("td");
1376         this._timelineCell.className = "timeline-column";
1377         this._element.appendChild(this._timelineCell);
1378         this._timelineCell.appendChild(this._graphElement);
1379     },
1380
1381     refreshResource: function()
1382     {
1383         this._refreshNameCell();
1384
1385         this._methodCell.textContent = this._resource.requestMethod;
1386
1387         this._refreshStatusCell();
1388
1389         if (this._resource.mimeType) {
1390             this._typeCell.removeStyleClass("network-dim-cell");
1391             this._typeCell.textContent = this._resource.mimeType;
1392         } else {
1393             this._typeCell.addStyleClass("network-dim-cell");
1394             this._typeCell.textContent = WebInspector.UIString("Pending");
1395         }
1396
1397         this._refreshSizeCell();
1398         this._refreshTimeCell();
1399
1400         if (this._resource.cached)
1401             this._graphElement.addStyleClass("resource-cached");
1402
1403         this._element.addStyleClass("network-item");
1404         if (!this._element.hasStyleClass("network-category-" + this._resource.category.name)) {
1405             this._element.removeMatchingStyleClasses("network-category-\\w+");
1406             this._element.addStyleClass("network-category-" + this._resource.category.name);
1407         }
1408     },
1409
1410     _refreshNameCell: function()
1411     {
1412         this._nameCell.removeChildren();
1413
1414         if (this._resource.category === WebInspector.resourceCategories.images) {
1415             var previewImage = document.createElement("img");
1416             previewImage.className = "image-network-icon-preview";
1417
1418             function onResourceContent()
1419             {
1420                 previewImage.src = this._resource.contentURL;
1421             }
1422             if (Preferences.useDataURLForResourceImageIcons)
1423                 this._resource.requestContent(onResourceContent.bind(this));
1424             else
1425                 previewImage.src = this._resource.url;
1426
1427             var iconElement = document.createElement("div");
1428             iconElement.className = "icon";
1429             iconElement.appendChild(previewImage);
1430         } else {
1431             var iconElement = document.createElement("img");
1432             iconElement.className = "icon";
1433         }
1434         this._nameCell.appendChild(iconElement);
1435         this._nameCell.appendChild(document.createTextNode(this._fileName()));
1436
1437
1438         var subtitle = this._resource.displayDomain;
1439
1440         if (this._resource.path && this._resource.lastPathComponent) {
1441             var lastPathComponentIndex = this._resource.path.lastIndexOf("/" + this._resource.lastPathComponent);
1442             if (lastPathComponentIndex != -1)
1443                 subtitle += this._resource.path.substring(0, lastPathComponentIndex);
1444         }
1445
1446         this._appendSubtitle(this._nameCell, subtitle);
1447         this._nameCell.title = this._resource.url;
1448     },
1449
1450     _fileName: function()
1451     {
1452         var fileName = this._resource.displayName;
1453         if (this._resource.queryString)
1454             fileName += "?" + this._resource.queryString;
1455         return fileName;
1456     },
1457
1458     _refreshStatusCell: function()
1459     {
1460         this._statusCell.removeChildren();
1461
1462         var fromCache = this._resource.cached;
1463         if (fromCache) {
1464             this._statusCell.textContent = WebInspector.UIString("(from cache)");
1465             this._statusCell.addStyleClass("network-dim-cell");
1466             return;
1467         }
1468
1469         this._statusCell.removeStyleClass("network-dim-cell");
1470         if (this._resource.statusCode) {
1471             this._statusCell.appendChild(document.createTextNode(this._resource.statusCode));
1472             this._statusCell.removeStyleClass("network-dim-cell");
1473             this._appendSubtitle(this._statusCell, this._resource.statusText);
1474             this._statusCell.title = this._resource.statusCode + " " + this._resource.statusText;
1475         } else {
1476             this._statusCell.addStyleClass("network-dim-cell");
1477             this._statusCell.textContent = WebInspector.UIString("Pending");
1478         }
1479     },
1480
1481     _refreshSizeCell: function()
1482     {
1483         var resourceSize = typeof this._resource.resourceSize === "number" ? Number.bytesToString(this._resource.resourceSize) : "?";
1484         var transferSize = typeof this._resource.transferSize === "number" ? Number.bytesToString(this._resource.transferSize) : "?";
1485         var fromCache = this._resource.cached;
1486         this._sizeCell.textContent = !fromCache ? resourceSize : WebInspector.UIString("(from cache)");
1487         if (fromCache)
1488             this._sizeCell.addStyleClass("network-dim-cell");
1489         else
1490             this._sizeCell.removeStyleClass("network-dim-cell");
1491         if (!fromCache)
1492             this._appendSubtitle(this._sizeCell, transferSize);
1493     },
1494
1495     _refreshTimeCell: function()
1496     {
1497         if (this._resource.duration > 0) {
1498             this._timeCell.removeStyleClass("network-dim-cell");
1499             this._timeCell.textContent = Number.secondsToString(this._resource.duration);
1500             this._appendSubtitle(this._timeCell, Number.secondsToString(this._resource.latency));
1501         } else {
1502             this._timeCell.addStyleClass("network-dim-cell");
1503             this._timeCell.textContent = WebInspector.UIString("Pending");
1504         }
1505     },
1506
1507     _appendSubtitle: function(cellElement, subtitleText)
1508     {
1509         var subtitleElement = document.createElement("div");
1510         subtitleElement.className = "network-cell-subtitle";
1511         subtitleElement.textContent = subtitleText;
1512         cellElement.appendChild(subtitleElement);
1513     },
1514
1515     refreshGraph: function(calculator)
1516     {
1517         var percentages = calculator.computeBarGraphPercentages(this._resource);
1518         this._percentages = percentages;
1519
1520         this._barAreaElement.removeStyleClass("hidden");
1521
1522         if (!this._graphElement.hasStyleClass("network-category-" + this._resource.category.name)) {
1523             this._graphElement.removeMatchingStyleClasses("network-category-\\w+");
1524             this._graphElement.addStyleClass("network-category-" + this._resource.category.name);
1525         }
1526
1527         this._barLeftElement.style.setProperty("left", percentages.start + "%");
1528         this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
1529
1530         this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
1531         this._barRightElement.style.setProperty("left", percentages.middle + "%");
1532
1533         var labels = calculator.computeBarGraphLabels(this._resource);
1534         this._labelLeftElement.textContent = labels.left;
1535         this._labelRightElement.textContent = labels.right;
1536
1537         var tooltip = (labels.tooltip || "");
1538         this._barLeftElement.title = tooltip;
1539         this._labelLeftElement.title = tooltip;
1540         this._labelRightElement.title = tooltip;
1541         this._barRightElement.title = tooltip;
1542     },
1543
1544     _refreshLabelPositions: function()
1545     {
1546         if (!this._percentages)
1547             return;
1548         this._labelLeftElement.style.removeProperty("left");
1549         this._labelLeftElement.style.removeProperty("right");
1550         this._labelLeftElement.removeStyleClass("before");
1551         this._labelLeftElement.removeStyleClass("hidden");
1552
1553         this._labelRightElement.style.removeProperty("left");
1554         this._labelRightElement.style.removeProperty("right");
1555         this._labelRightElement.removeStyleClass("after");
1556         this._labelRightElement.removeStyleClass("hidden");
1557
1558         const labelPadding = 10;
1559         const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
1560         const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;
1561
1562         if (this._barLeftElement) {
1563             var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
1564             var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
1565         } else {
1566             var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
1567             var rightBarWidth = barRightElementOffsetWidth - labelPadding;
1568         }
1569
1570         const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
1571         const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;
1572
1573         const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
1574         const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
1575         const graphElementOffsetWidth = this._graphElement.offsetWidth;
1576
1577         if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
1578             var leftHidden = true;
1579
1580         if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
1581             var rightHidden = true;
1582
1583         if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
1584             // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
1585             if (labelBefore && !labelAfter)
1586                 leftHidden = true;
1587             else if (labelAfter && !labelBefore)
1588                 rightHidden = true;
1589         }
1590
1591         if (labelBefore) {
1592             if (leftHidden)
1593                 this._labelLeftElement.addStyleClass("hidden");
1594             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
1595             this._labelLeftElement.addStyleClass("before");
1596         } else {
1597             this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
1598             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
1599         }
1600
1601         if (labelAfter) {
1602             if (rightHidden)
1603                 this._labelRightElement.addStyleClass("hidden");
1604             this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
1605             this._labelRightElement.addStyleClass("after");
1606         } else {
1607             this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
1608             this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
1609         }
1610     }
1611 }
1612
1613 WebInspector.NetworkDataGridNode.NameComparator = function(a, b)
1614 {
1615     var aFileName = a._resource.displayName + (a._resource.queryString ? a._resource.queryString : "");
1616     var bFileName = b._resource.displayName + (b._resource.queryString ? b._resource.queryString : "");
1617     if (aFileName > bFileName)
1618         return 1;
1619     if (bFileName > aFileName)
1620         return -1;
1621     return 0;
1622 }
1623
1624 WebInspector.NetworkDataGridNode.SizeComparator = function(a, b)
1625 {
1626     if (b._resource.cached && !a._resource.cached)
1627         return 1;
1628     if (a._resource.cached && !b._resource.cached)
1629         return -1;
1630
1631     if (a._resource.resourceSize === b._resource.resourceSize)
1632         return 0;
1633
1634     return a._resource.resourceSize - b._resource.resourceSize;
1635 }
1636
1637 WebInspector.NetworkDataGridNode.ResourcePropertyComparator = function(propertyName, revert, a, b)
1638 {
1639     var aValue = a._resource[propertyName];
1640     var bValue = b._resource[propertyName];
1641     if (aValue > bValue)
1642         return revert ? -1 : 1;
1643     if (bValue > aValue)
1644         return revert ? 1 : -1;
1645     return 0;
1646 }
1647
1648 WebInspector.NetworkDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
1649
1650 WebInspector.NetworkTotalGridNode = function(element)
1651 {
1652     this._summaryBarElement = element;
1653     WebInspector.DataGridNode.call(this, {summaryRow: true});
1654 }
1655
1656 WebInspector.NetworkTotalGridNode.prototype = {
1657     createCells: function()
1658     {
1659         var td = document.createElement("td");
1660         td.setAttribute("colspan", 7);
1661         td.className = "network-summary";
1662         td.appendChild(this._summaryBarElement);
1663         this._element.appendChild(td);
1664     }
1665 }
1666
1667 WebInspector.NetworkTotalGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;