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