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