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