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