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