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