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