Web Inspector: speed-up Network panel. Change _staleResources type from array to...
[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.NetworkLogView = function(parentElement)
32 {
33     // FIXME: some of the styles should be loaded on demand by components that need them.
34     var styles = [
35         "inspectorCommon.css",
36         "dataGrid.css",
37         "networkLogView.css"
38     ];
39     WebInspector.IFrameView.call(this, parentElement, styles);
40
41     this._allowResourceSelection = false;
42     this._resources = [];
43     this._resourcesById = {};
44     this._resourcesByURL = {};
45     this._staleResources = {};
46     this._resourceGridNodes = {};
47     this._lastResourceGridNodeId = 0;
48     this._mainResourceLoadTime = -1;
49     this._mainResourceDOMContentTime = -1;
50     this._hiddenCategories = {};
51     this._matchedResources = [];
52     this._matchedResourcesMap = {};
53     this._currentMatchedResourceIndex = -1;
54
55     this._categories = WebInspector.resourceCategories;
56
57     this._createStatusbarButtons();
58     this._createFilterStatusBarItems();
59
60     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceStarted, this._onResourceStarted, this);
61     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdated, this._onResourceUpdated, this);
62     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceFinished, this._onResourceUpdated, this);
63
64     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this);
65     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.OnLoad, this._onLoadEventFired, this);
66     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, this._domContentLoadedEventFired, this);
67 }
68
69 WebInspector.NetworkLogView.prototype = {
70     initializeView: function()
71     {
72         this.element.id = "network-container";
73
74         this._createSortingFunctions();
75         this._createTable();
76         this._createTimelineGrid();
77         this._createSummaryBar();
78
79         if (!this.useLargeRows)
80             this._setLargerResources(this.useLargeRows);
81
82         this._allowPopover = true;
83         this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
84         // Enable faster hint.
85         this._popoverHelper.setTimeout(100);
86
87         this.calculator = new WebInspector.NetworkTransferTimeCalculator();
88         this._filter(this._filterAllElement, false);
89
90         this.switchToDetailedView();
91     },
92
93     get statusBarItems()
94     {
95         return [this._largerResourcesButton.element, this._preserveLogToggle.element, this._clearButton.element, this._filterBarElement];
96     },
97
98     get useLargeRows()
99     {
100         return WebInspector.settings.resourcesLargeRows.get();
101     },
102
103     set allowPopover(flag)
104     {
105         this._allowPopover = flag;
106     },
107
108     get allowResourceSelection()
109     {
110         return this._allowResourceSelection;
111     },
112
113     set allowResourceSelection(flag)
114     {
115         this._allowResourceSelection = !!flag;
116     },
117
118     elementsToRestoreScrollPositionsFor: function()
119     {
120         if (!this._dataGrid) // Not initialized yet.
121             return [];
122         return [this._dataGrid.scrollContainer];
123     },
124
125     onResize: function()
126     {
127         this._dataGrid.updateWidths();
128         this._updateOffscreenRows();
129     },
130
131     _createTimelineGrid: function()
132     {
133         this._timelineGrid = new WebInspector.TimelineGrid();
134         this._timelineGrid.element.addStyleClass("network-timeline-grid");
135         this._dataGrid.element.appendChild(this._timelineGrid.element);
136     },
137
138     _createTable: function()
139     {
140         var columns;
141         if (Preferences.showNetworkPanelInitiatorColumn)
142             columns = {name: {}, method: {}, status: {}, type: {}, initiator: {}, size: {}, time: {}, timeline: {}};
143         else
144             columns = {name: {}, method: {}, status: {}, type: {}, size: {}, time: {}, timeline: {}};
145         columns.name.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path"));
146         columns.name.sortable = true;
147         columns.name.width = "20%";
148         columns.name.disclosure = true;
149
150         columns.method.title = WebInspector.UIString("Method");
151         columns.method.sortable = true;
152         columns.method.width = "6%";
153
154         columns.status.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text"));
155         columns.status.sortable = true;
156         columns.status.width = "6%";
157
158         columns.type.title = WebInspector.UIString("Type");
159         columns.type.sortable = true;
160         columns.type.width = "6%";
161
162         if (Preferences.showNetworkPanelInitiatorColumn) {
163             columns.initiator.title = WebInspector.UIString("Initiator");
164             columns.initiator.sortable = true;
165             columns.initiator.width = "10%";
166         }
167
168         columns.size.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Content"));
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         if (Preferences.showNetworkPanelInitiatorColumn)
181             columns.timeline.width = "40%";
182         else
183             columns.timeline.width = "50%";
184         columns.timeline.sort = "ascending";
185
186         this._dataGrid = new WebInspector.DataGrid(columns);
187         this._dataGrid.resizeMethod = WebInspector.DataGrid.ResizeMethod.Last;
188         this._dataGrid.element.addStyleClass("network-log-grid");
189         this._dataGrid.element.addEventListener("contextmenu", this._contextMenu.bind(this), true);
190
191         this.element.appendChild(this._dataGrid.element);
192
193         // Event listeners need to be added _after_ we attach to the document, so that owner document is properly update.
194         this._dataGrid.addEventListener("sorting changed", this._sortItems, this);
195         this._dataGrid.addEventListener("width changed", this._updateDividersIfNeeded, this);
196         this._dataGrid.scrollContainer.addEventListener("scroll", this._updateOffscreenRows.bind(this));
197
198         this._patchTimelineHeader();
199     },
200
201     _makeHeaderFragment: function(title, subtitle)
202     {
203         var fragment = document.createDocumentFragment();
204         fragment.appendChild(document.createTextNode(title));
205         var subtitleDiv = document.createElement("div");
206         subtitleDiv.className = "network-header-subtitle";
207         subtitleDiv.textContent = subtitle;
208         fragment.appendChild(subtitleDiv);
209         return fragment;
210     },
211
212     _patchTimelineHeader: function()
213     {
214         var timelineSorting = document.createElement("select");
215
216         var option = document.createElement("option");
217         option.value = "startTime";
218         option.label = WebInspector.UIString("Timeline");
219         timelineSorting.appendChild(option);
220
221         option = document.createElement("option");
222         option.value = "startTime";
223         option.label = WebInspector.UIString("Start Time");
224         timelineSorting.appendChild(option);
225
226         option = document.createElement("option");
227         option.value = "responseTime";
228         option.label = WebInspector.UIString("Response Time");
229         timelineSorting.appendChild(option);
230
231         option = document.createElement("option");
232         option.value = "endTime";
233         option.label = WebInspector.UIString("End Time");
234         timelineSorting.appendChild(option);
235
236         option = document.createElement("option");
237         option.value = "duration";
238         option.label = WebInspector.UIString("Duration");
239         timelineSorting.appendChild(option);
240
241         option = document.createElement("option");
242         option.value = "latency";
243         option.label = WebInspector.UIString("Latency");
244         timelineSorting.appendChild(option);
245
246         var header = this._dataGrid.headerTableHeader("timeline");
247         header.replaceChild(timelineSorting, header.firstChild);
248
249         timelineSorting.addEventListener("click", function(event) { event.stopPropagation() }, false);
250         timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false);
251         this._timelineSortSelector = timelineSorting;
252     },
253
254     _createSortingFunctions: function()
255     {
256         this._sortingFunctions = {};
257         this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator;
258         this._sortingFunctions.method = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "method", false);
259         this._sortingFunctions.status = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "statusCode", false);
260         this._sortingFunctions.type = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "mimeType", false);
261         this._sortingFunctions.initiator = WebInspector.NetworkDataGridNode.InitiatorComparator;
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         this._removeAllNodeHighlights();
286         var columnIdentifier = this._dataGrid.sortColumnIdentifier;
287         if (columnIdentifier === "timeline") {
288             this._sortByTimeline();
289             return;
290         }
291         var sortingFunction = this._sortingFunctions[columnIdentifier];
292         if (!sortingFunction)
293             return;
294
295         this._dataGrid.sortNodes(sortingFunction, this._dataGrid.sortOrder === "descending");
296         this._timelineSortSelector.selectedIndex = 0;
297         this._updateOffscreenRows();
298
299         this.performSearch(null, true);
300     },
301
302     _sortByTimeline: function()
303     {
304         this._removeAllNodeHighlights();
305         var selectedIndex = this._timelineSortSelector.selectedIndex;
306         if (!selectedIndex)
307             selectedIndex = 1; // Sort by start time by default.
308         var selectedOption = this._timelineSortSelector[selectedIndex];
309         var value = selectedOption.value;
310
311         var sortingFunction = this._sortingFunctions[value];
312         this._dataGrid.sortNodes(sortingFunction);
313         this.calculator = this._calculators[value];
314         if (this.calculator.startAtZero)
315             this._timelineGrid.hideEventDividers();
316         else
317             this._timelineGrid.showEventDividers();
318         this._dataGrid.markColumnAsSortedBy("timeline", "ascending");
319         this._updateOffscreenRows();
320     },
321
322     _createFilterStatusBarItems: function()
323     {
324         var filterBarElement = document.createElement("div");
325         filterBarElement.className = "scope-bar status-bar-item";
326         filterBarElement.id = "network-filter";
327
328         function createFilterElement(category, label)
329         {
330             var categoryElement = document.createElement("li");
331             categoryElement.category = category;
332             categoryElement.className = category;
333             categoryElement.appendChild(document.createTextNode(label));
334             categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
335             filterBarElement.appendChild(categoryElement);
336
337             return categoryElement;
338         }
339
340         this._filterAllElement = createFilterElement.call(this, "all", WebInspector.UIString("All"));
341
342         // Add a divider
343         var dividerElement = document.createElement("div");
344         dividerElement.addStyleClass("scope-bar-divider");
345         filterBarElement.appendChild(dividerElement);
346
347         for (var category in this._categories)
348             createFilterElement.call(this, category, this._categories[category].title);
349         this._filterBarElement = filterBarElement;
350     },
351
352     _createSummaryBar: function()
353     {
354         var tbody = this._dataGrid.dataTableBody;
355         var tfoot = document.createElement("tfoot");
356         var tr = tfoot.createChild("tr", "revealed network-summary-bar");
357         var td = tr.createChild("td");
358         td.setAttribute("colspan", 7);
359         tbody.parentNode.insertBefore(tfoot, tbody);
360         this._summaryBarElement = td;
361     },
362
363     _updateSummaryBar: function()
364     {
365         var requestsNumber = this._resources.length;
366
367         if (!requestsNumber) {
368             if (this._summaryBarElement._isDisplayingWarning)
369                 return;
370             this._summaryBarElement._isDisplayingWarning = true;
371
372             var img = document.createElement("img");
373             img.src = "Images/warningIcon.png";
374             this._summaryBarElement.removeChildren();
375             this._summaryBarElement.appendChild(img);
376             this._summaryBarElement.appendChild(document.createTextNode(
377                 WebInspector.UIString("No requests captured. Reload the page to see detailed information on the network activity.")));
378             return;
379         }
380         delete this._summaryBarElement._isDisplayingWarning;
381
382         var transferSize = 0;
383         var selectedRequestsNumber = 0;
384         var selectedTransferSize = 0;
385         var baseTime = -1;
386         var maxTime = -1;
387         for (var i = 0; i < this._resources.length; ++i) {
388             var resource = this._resources[i];
389             var resourceTransferSize = (resource.cached || !resource.transferSize) ? 0 : resource.transferSize;
390             transferSize += resourceTransferSize;
391             if (!this._hiddenCategories.all || !this._hiddenCategories[resource.category.name]) {
392                 selectedRequestsNumber++;
393                 selectedTransferSize += resourceTransferSize;
394             }
395             if (resource === WebInspector.mainResource)
396                 baseTime = resource.startTime;
397             if (resource.endTime > maxTime)
398                 maxTime = resource.endTime;
399         }
400         var text = "";
401         if (this._hiddenCategories.all) {
402             text += String.sprintf(WebInspector.UIString("%d / %d requests"), selectedRequestsNumber, requestsNumber);
403             text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s / %s transferred"), Number.bytesToString(selectedTransferSize), Number.bytesToString(transferSize));
404         } else {
405             text += String.sprintf(WebInspector.UIString("%d requests"), requestsNumber);
406             text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize));
407         }
408         if (baseTime !== -1 && this._mainResourceLoadTime !== -1 && this._mainResourceDOMContentTime !== -1 && this._mainResourceDOMContentTime > baseTime) {
409             text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s (onload: %s, DOMContentLoaded: %s)"),
410                         Number.secondsToString(maxTime - baseTime),
411                         Number.secondsToString(this._mainResourceLoadTime - baseTime),
412                         Number.secondsToString(this._mainResourceDOMContentTime - baseTime));
413         }
414         this._summaryBarElement.textContent = text;
415     },
416
417     _showCategory: function(category)
418     {
419         this._dataGrid.element.addStyleClass("filter-" + category);
420         delete this._hiddenCategories[category];
421     },
422
423     _hideCategory: function(category)
424     {
425         this._dataGrid.element.removeStyleClass("filter-" + category);
426         this._hiddenCategories[category] = true;
427     },
428
429     _updateFilter: function(e)
430     {
431         this._removeAllNodeHighlights();
432         var isMac = WebInspector.isMac();
433         var selectMultiple = false;
434         if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
435             selectMultiple = true;
436         if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
437             selectMultiple = true;
438
439         this._filter(e.target, selectMultiple);
440         this.performSearch(null, true);
441         this._updateSummaryBar();
442     },
443
444     _filter: function(target, selectMultiple)
445     {
446         function unselectAll()
447         {
448             for (var i = 0; i < this._filterBarElement.childNodes.length; ++i) {
449                 var child = this._filterBarElement.childNodes[i];
450                 if (!child.category)
451                     continue;
452
453                 child.removeStyleClass("selected");
454                 this._hideCategory(child.category);
455             }
456         }
457
458         if (target.category === this._filterAllElement) {
459             if (target.hasStyleClass("selected")) {
460                 // We can't unselect All, so we break early here
461                 return;
462             }
463
464             // If All wasn't selected, and now is, unselect everything else.
465             unselectAll.call(this);
466         } else {
467             // Something other than All is being selected, so we want to unselect All.
468             if (this._filterAllElement.hasStyleClass("selected")) {
469                 this._filterAllElement.removeStyleClass("selected");
470                 this._hideCategory("all");
471             }
472         }
473
474         if (!selectMultiple) {
475             // If multiple selection is off, we want to unselect everything else
476             // and just select ourselves.
477             unselectAll.call(this);
478
479             target.addStyleClass("selected");
480             this._showCategory(target.category);
481             this._updateOffscreenRows();
482             return;
483         }
484
485         if (target.hasStyleClass("selected")) {
486             // If selectMultiple is turned on, and we were selected, we just
487             // want to unselect ourselves.
488             target.removeStyleClass("selected");
489             this._hideCategory(target.category);
490         } else {
491             // If selectMultiple is turned on, and we weren't selected, we just
492             // want to select ourselves.
493             target.addStyleClass("selected");
494             this._showCategory(target.category);
495         }
496         this._updateOffscreenRows();
497     },
498
499     _scheduleRefresh: function()
500     {
501         if (this._needsRefresh)
502             return;
503
504         this._needsRefresh = true;
505
506         if (this.visible && !("_refreshTimeout" in this))
507             this._refreshTimeout = setTimeout(this.refresh.bind(this), 500);
508     },
509
510     _updateDividersIfNeeded: function(force)
511     {
512         if (!this._dataGrid)
513             return;
514         var timelineColumn = this._dataGrid.columns.timeline;
515         for (var i = 0; i < this._dataGrid.resizers.length; ++i) {
516             if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnID) {
517                 // Position timline grid location.
518                 this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left;
519                 this._timelineGrid.element.style.right = "18px";
520             }
521         }
522
523         var proceed = true;
524         if (!this.visible) {
525             this._scheduleRefresh();
526             proceed = false;
527         } else
528             proceed = this._timelineGrid.updateDividers(force, this.calculator);
529
530         if (!proceed)
531             return;
532
533         if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) {
534             // If our current sorting method starts at zero, that means it shows all
535             // resources starting at the same point, and so onLoad event and DOMContent
536             // event lines really wouldn't make much sense here, so don't render them.
537             // Additionally, if the calculator doesn't have the computePercentageFromEventTime
538             // function defined, we are probably sorting by size, and event times aren't relevant
539             // in this case.
540             return;
541         }
542
543         this._timelineGrid.removeEventDividers();
544         if (this._mainResourceLoadTime !== -1) {
545             var percent = this.calculator.computePercentageFromEventTime(this._mainResourceLoadTime);
546
547             var loadDivider = document.createElement("div");
548             loadDivider.className = "network-event-divider network-red-divider";
549
550             var loadDividerPadding = document.createElement("div");
551             loadDividerPadding.className = "network-event-divider-padding";
552             loadDividerPadding.title = WebInspector.UIString("Load event fired");
553             loadDividerPadding.appendChild(loadDivider);
554             loadDividerPadding.style.left = percent + "%";
555             this._timelineGrid.addEventDivider(loadDividerPadding);
556         }
557
558         if (this._mainResourceDOMContentTime !== -1) {
559             var percent = this.calculator.computePercentageFromEventTime(this._mainResourceDOMContentTime);
560
561             var domContentDivider = document.createElement("div");
562             domContentDivider.className = "network-event-divider network-blue-divider";
563
564             var domContentDividerPadding = document.createElement("div");
565             domContentDividerPadding.className = "network-event-divider-padding";
566             domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired");
567             domContentDividerPadding.appendChild(domContentDivider);
568             domContentDividerPadding.style.left = percent + "%";
569             this._timelineGrid.addEventDivider(domContentDividerPadding);
570         }
571     },
572
573     _refreshIfNeeded: function()
574     {
575         if (this._needsRefresh)
576             this.refresh();
577     },
578
579     _invalidateAllItems: function()
580     {
581         for (var i = 0; i < this._resources.length; ++i) {
582             var resource = this._resources[i];
583             this._staleResources[resource.requestId] = resource;
584         }
585     },
586
587     get calculator()
588     {
589         return this._calculator;
590     },
591
592     set calculator(x)
593     {
594         if (!x || this._calculator === x)
595             return;
596
597         this._calculator = x;
598         this._calculator.reset();
599
600         this._invalidateAllItems();
601         this.refresh();
602     },
603
604     _resourceGridNode: function(resource)
605     {
606         return this._resourceGridNodes[resource.__gridNodeId];
607     },
608
609     _createResourceGridNode: function(resource)
610     {
611         var node = new WebInspector.NetworkDataGridNode(this, resource);
612         resource.__gridNodeId = this._lastResourceGridNodeId++;
613         this._resourceGridNodes[resource.__gridNodeId] = node;
614         return node;
615     },
616
617     _createStatusbarButtons: function()
618     {
619         this._preserveLogToggle = new WebInspector.StatusBarButton(WebInspector.UIString("Preserve Log upon Navigation"), "record-profile-status-bar-item");
620         this._preserveLogToggle.addEventListener("click", this._onPreserveLogClicked.bind(this), false);
621
622         this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
623         this._clearButton.addEventListener("click", this._reset.bind(this), false);
624
625         this._largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item");
626         this._largerResourcesButton.toggled = WebInspector.settings.resourcesLargeRows.get();
627         this._largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
628     },
629
630     _onLoadEventFired: function(event)
631     {
632         this._mainResourceLoadTime = event.data || -1;
633         // Schedule refresh to update boundaries and draw the new line.
634         this._scheduleRefresh(true);
635     },
636
637     _domContentLoadedEventFired: function(event)
638     {
639         this._mainResourceDOMContentTime = event.data || -1;
640         // Schedule refresh to update boundaries and draw the new line.
641         this._scheduleRefresh(true);
642     },
643
644     wasShown: function()
645     {
646         WebInspector.IFrameView.prototype.wasShown.call(this);
647         this._refreshIfNeeded();
648     },
649
650     willHide: function()
651     {
652         WebInspector.IFrameView.prototype.willHide.call(this);
653         this._popoverHelper.hidePopover();
654     },
655
656     refresh: function()
657     {
658         this._needsRefresh = false;
659         if ("_refreshTimeout" in this) {
660             clearTimeout(this._refreshTimeout);
661             delete this._refreshTimeout;
662         }
663
664         this._removeAllNodeHighlights();
665         var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow();
666         var boundariesChanged = false;
667         if (this.calculator.updateBoundariesForEventTime) {
668             boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainResourceLoadTime) || boundariesChanged;
669             boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainResourceDOMContentTime) || boundariesChanged;
670         }
671
672         for (var resourceId in this._staleResources) {
673             var resource = this._staleResources[resourceId];
674             var node = this._resourceGridNode(resource);
675             if (!node) {
676                 // Create the timeline tree element and graph.
677                 node = this._createResourceGridNode(resource);
678                 this._dataGrid.appendChild(node);
679             }
680             node.refreshResource();
681
682             if (this.calculator.updateBoundaries(resource))
683                 boundariesChanged = true;
684
685             if (!node.isFilteredOut())
686                 this._updateHighlightIfMatched(resource);
687         }
688
689         if (boundariesChanged) {
690             // The boundaries changed, so all item graphs are stale.
691             this._invalidateAllItems();
692         }
693
694         for (var resourceId in this._staleResources)
695             this._resourceGridNode(this._staleResources[resourceId]).refreshGraph(this.calculator);
696
697         this._staleResources = {};
698         this._sortItems();
699         this._updateSummaryBar();
700         this._dataGrid.updateWidths();
701         // FIXME: evaluate performance impact of moving this before a call to sortItems()
702         if (wasScrolledToLastRow)
703             this._dataGrid.scrollToLastRow();
704     },
705
706     _onPreserveLogClicked: function(e)
707     {
708         this._preserveLogToggle.toggled = !this._preserveLogToggle.toggled;
709     },
710
711     _reset: function()
712     {
713         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ViewCleared);
714
715         this._clearSearchMatchedList();
716         if (this._popoverHelper)
717             this._popoverHelper.hidePopover();
718
719         if (this._calculator)
720             this._calculator.reset();
721
722         this._resources = [];
723         this._resourcesById = {};
724         this._resourcesByURL = {};
725         this._staleResources = {};
726         this._resourceGridNodes = {};
727
728         if (this._dataGrid) {
729             this._dataGrid.removeChildren();
730             this._updateDividersIfNeeded(true);
731             this._updateSummaryBar();
732         }
733
734         this._mainResourceLoadTime = -1;
735         this._mainResourceDOMContentTime = -1;
736
737     },
738
739     get resources()
740     {
741         return this._resources;
742     },
743
744     resourceById: function(id)
745     {
746         return this._resourcesById[id];
747     },
748
749     _onResourceStarted: function(event)
750     {
751         this._appendResource(event.data);
752     },
753
754     _appendResource: function(resource)
755     {
756         this._resources.push(resource);
757
758         // In case of redirect request id is reassigned to a redirected
759         // resource and we need to update _resourcesById ans search results.
760         if (this._resourcesById[resource.requestId]) {
761             var oldResource = resource.redirects[resource.redirects.length - 1];
762             this._resourcesById[oldResource.requestId] = oldResource;
763
764             this._updateSearchMatchedListAfterRequestIdChanged(resource.requestId, oldResource.requestId);
765         }
766         this._resourcesById[resource.requestId] = resource;
767
768         this._resourcesByURL[resource.url] = resource;
769
770         // Pull all the redirects of the main resource upon commit load.
771         if (resource.redirects) {
772             for (var i = 0; i < resource.redirects.length; ++i)
773                 this._refreshResource(resource.redirects[i]);
774         }
775
776         this._refreshResource(resource);
777     },
778
779     _onResourceUpdated: function(event)
780     {
781         this._refreshResource(event.data);
782     },
783
784     _refreshResource: function(resource)
785     {
786         this._staleResources[resource.requestId] = resource;
787         this._scheduleRefresh();
788     },
789
790     clear: function()
791     {
792         if (this._preserveLogToggle.toggled)
793             return;
794         this._reset();
795     },
796
797     _frameNavigated: function(event)
798     {
799         if (!event.data.isMainFrame)
800             return;
801
802         var loaderId = event.data.loaderId;
803         // Main frame committed load.
804         if (this._preserveLogToggle.toggled)
805             return;
806
807         // Preserve provisional load resources.
808         var resourcesToPreserve = [];
809         for (var i = 0; i < this._resources.length; ++i) {
810             var resource = this._resources[i];
811             if (resource.loaderId === loaderId)
812                 resourcesToPreserve.push(resource);
813         }
814
815         this._reset();
816
817         // Restore preserved items.
818         for (var i = 0; i < resourcesToPreserve.length; ++i)
819             this._appendResource(resourcesToPreserve[i]);
820     },
821
822     switchToDetailedView: function()
823     {
824         if (!this._dataGrid)
825             return;
826         if (this._dataGrid.selectedNode)
827             this._dataGrid.selectedNode.selected = false;
828
829         this.element.removeStyleClass("brief-mode");
830
831         this._dataGrid.showColumn("method");
832         this._dataGrid.showColumn("status");
833         this._dataGrid.showColumn("type");
834         if (Preferences.showNetworkPanelInitiatorColumn)
835             this._dataGrid.showColumn("initiator");
836         this._dataGrid.showColumn("size");
837         this._dataGrid.showColumn("time");
838         this._dataGrid.showColumn("timeline");
839
840         var widths = {};
841         widths.name = 20;
842         widths.method = 6;
843         widths.status = 6;
844         widths.type = 6;
845         if (Preferences.showNetworkPanelInitiatorColumn)
846             widths.initiator = 10;
847         widths.size = 6;
848         widths.time = 6;
849         if (Preferences.showNetworkPanelInitiatorColumn)
850             widths.timeline = 40;
851         else
852             widths.timeline = 50;
853
854         this._dataGrid.applyColumnWidthsMap(widths);
855     },
856
857     switchToBriefView: function()
858     {
859         this.element.addStyleClass("brief-mode");
860         this._removeAllNodeHighlights();
861
862         this._dataGrid.hideColumn("method");
863         this._dataGrid.hideColumn("status");
864         this._dataGrid.hideColumn("type");
865         if (Preferences.showNetworkPanelInitiatorColumn)
866             this._dataGrid.hideColumn("initiator");
867         this._dataGrid.hideColumn("size");
868         this._dataGrid.hideColumn("time");
869         this._dataGrid.hideColumn("timeline");
870
871         var widths = {};
872         widths.name = 100;
873         this._dataGrid.applyColumnWidthsMap(widths);
874
875         this._popoverHelper.hidePopover();
876     },
877
878     _toggleLargerResources: function()
879     {
880         WebInspector.settings.resourcesLargeRows.set(!WebInspector.settings.resourcesLargeRows.get());
881         this._setLargerResources(WebInspector.settings.resourcesLargeRows.get());
882     },
883
884     _setLargerResources: function(enabled)
885     {
886         this._largerResourcesButton.toggled = enabled;
887         if (!enabled) {
888             this._largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
889             this._dataGrid.element.addStyleClass("small");
890             this._timelineGrid.element.addStyleClass("small");
891         } else {
892             this._largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
893             this._dataGrid.element.removeStyleClass("small");
894             this._timelineGrid.element.removeStyleClass("small");
895         }
896         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, { largeRows: enabled });
897         this._updateOffscreenRows();
898     },
899
900     _getPopoverAnchor: function(element)
901     {
902         if (!this._allowPopover)
903             return;
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, popover)
912     {
913         var resource = anchor.parentElement.resource;
914         var tableElement = WebInspector.ResourceTimingView.createTimingTable(resource);
915         popover.show(tableElement, anchor);
916     },
917
918     _toggleGridMode: function()
919     {
920         if (this._viewingResourceMode) {
921             this._viewingResourceMode = false;
922             this.element.removeStyleClass("viewing-resource");
923             this._viewsContainerElement.addStyleClass("hidden");
924             this.sidebarElement.style.right = 0;
925             this.sidebarElement.style.removeProperty("width");
926             if (this._dataGrid.selectedNode)
927                 this._dataGrid.selectedNode.selected = false;
928         }
929
930         this._dataGrid.showColumn("method");
931         this._dataGrid.showColumn("status");
932         this._dataGrid.showColumn("type");
933         if (Preferences.showNetworkPanelInitiatorColumn)
934             this._dataGrid.showColumn("initiator");
935         this._dataGrid.showColumn("size");
936         this._dataGrid.showColumn("time");
937
938         var widths = {};
939         widths.name = 20;
940         widths.method = 6;
941         widths.status = 6;
942         widths.type = 6;
943         if (Preferences.showNetworkPanelInitiatorColumn)
944             widths.initiator = 10;
945         widths.size = 6;
946         widths.time = 6;
947         if (Preferences.showNetworkPanelInitiatorColumn)
948             widths.timeline = 40;
949         else
950             widths.timeline = 50;
951
952         this._dataGrid.showColumn("timeline");
953         this._dataGrid.applyColumnWidthsMap(widths);
954     },
955
956     _toggleViewingResourceMode: function()
957     {
958         if (this._viewingResourceMode)
959             return;
960         this._viewingResourceMode = true;
961
962         this.element.addStyleClass("viewing-resource");
963
964         this._dataGrid.hideColumn("method");
965         this._dataGrid.hideColumn("status");
966         this._dataGrid.hideColumn("type");
967         if (Preferences.showNetworkPanelInitiatorColumn)
968             this._dataGrid.hideColumn("initiator");
969         this._dataGrid.hideColumn("size");
970         this._dataGrid.hideColumn("time");
971         this._dataGrid.hideColumn("timeline");
972
973         this._viewsContainerElement.removeStyleClass("hidden");
974         this.restoreSidebarWidth();
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.copyLinkAddressLabel(), 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(this._resources)).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(this._resources)).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     _updateOffscreenRows: function(e)
1071     {
1072         var dataTableBody = this._dataGrid.dataTableBody;
1073         var rows = dataTableBody.children;
1074         var recordsCount = rows.length;
1075         if (recordsCount < 2)
1076             return;  // Filler row only.
1077
1078         var visibleTop = this._dataGrid.scrollContainer.scrollTop;
1079         var visibleBottom = visibleTop + this._dataGrid.scrollContainer.offsetHeight;
1080
1081         var rowHeight = 0;
1082
1083         // Filler is at recordsCount - 1.
1084         var unfilteredRowIndex = 0;
1085         for (var i = 0; i < recordsCount - 1; ++i) {
1086             var row = rows[i];
1087
1088             var dataGridNode = this._dataGrid.dataGridNodeFromNode(row);
1089             if (dataGridNode.isFilteredOut()) {
1090                 row.removeStyleClass("offscreen");
1091                 continue;
1092             }
1093
1094             if (!rowHeight)
1095                 rowHeight = row.offsetHeight;
1096
1097             var rowIsVisible = unfilteredRowIndex * rowHeight < visibleBottom && (unfilteredRowIndex + 1) * rowHeight > visibleTop;
1098             if (rowIsVisible !== row.rowIsVisible) {
1099                 if (rowIsVisible)
1100                     row.removeStyleClass("offscreen");
1101                 else
1102                     row.addStyleClass("offscreen");
1103                 row.rowIsVisible = rowIsVisible;
1104             }
1105             unfilteredRowIndex++;
1106         }
1107     },
1108
1109     _matchResource: function(resource)
1110     {
1111         if (!this._searchRegExp)
1112             return -1;
1113
1114         if ((!resource.displayName || !resource.displayName.match(this._searchRegExp)) && !resource.folder.match(this._searchRegExp))
1115             return -1;
1116
1117         if (resource.requestId in this._matchedResourcesMap)
1118             return this._matchedResourcesMap[resource.requestId];
1119
1120         var matchedResourceIndex = this._matchedResources.length;
1121         this._matchedResourcesMap[resource.requestId] = matchedResourceIndex;
1122         this._matchedResources.push(resource.requestId);
1123
1124         return matchedResourceIndex;
1125     },
1126
1127     _clearSearchMatchedList: function()
1128     {
1129         this._matchedResources = [];
1130         this._matchedResourcesMap = {};
1131         this._highlightNthMatchedResource(-1, false);
1132     },
1133
1134     _updateSearchMatchedListAfterRequestIdChanged: function(oldRequestId, newRequestId)
1135     {
1136         var resourceIndex = this._matchedResourcesMap[oldRequestId];
1137         if (resourceIndex) {
1138             delete this._matchedResourcesMap[oldRequestId];
1139             this._matchedResourcesMap[newRequestId] = resourceIndex;
1140             this._matchedResources[resourceIndex] = newRequestId;
1141         }
1142     },
1143
1144     _updateHighlightIfMatched: function(resource)
1145     {
1146         var matchedResourceIndex = this._matchResource(resource);
1147         if (matchedResourceIndex === -1)
1148             return;
1149
1150         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedResources.length);
1151
1152         if (this._currentMatchedResourceIndex !== -1 && this._currentMatchedResourceIndex !== matchedResourceIndex)
1153             return;
1154
1155         this._highlightNthMatchedResource(matchedResourceIndex, false);
1156     },
1157
1158     _highlightNthMatchedResource: function(matchedResourceIndex, reveal)
1159     {
1160         if (this._highlightedSubstringChanges) {
1161             revertDomChanges(this._highlightedSubstringChanges);
1162             this._highlightedSubstringChanges = null;
1163         }
1164
1165         if (matchedResourceIndex === -1) {
1166             this._currentMatchedResourceIndex = matchedResourceIndex;
1167             return;
1168         }
1169
1170         var resource = this._resourcesById[this._matchedResources[matchedResourceIndex]];
1171         if (!resource)
1172             return;
1173
1174         var nameMatched = resource.displayName && resource.displayName.match(this._searchRegExp);
1175         var pathMatched = resource.path && resource.folder.match(this._searchRegExp);
1176         if (!nameMatched && pathMatched && !this._largerResourcesButton.toggled)
1177             this._toggleLargerResources();
1178
1179         var node = this._resourceGridNode(resource);
1180         if (node) {
1181             this._highlightedSubstringChanges = node._highlightMatchedSubstring(this._searchRegExp);
1182             if (reveal)
1183                 node.reveal();
1184             this._currentMatchedResourceIndex = matchedResourceIndex;
1185         }
1186         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._currentMatchedResourceIndex + 1);
1187     },
1188
1189     performSearch: function(searchQuery, sortOrFilterApplied)
1190     {
1191         var newMatchedResourceIndex = 0;
1192         var currentMatchedRequestId;
1193         if (this._currentMatchedResourceIndex !== -1)
1194             currentMatchedRequestId = this._matchedResources[this._currentMatchedResourceIndex];
1195
1196         if (!sortOrFilterApplied)
1197             this._searchRegExp = createSearchRegex(searchQuery);
1198
1199         this._clearSearchMatchedList();
1200
1201         var childNodes = this._dataGrid.dataTableBody.childNodes;
1202         var resourceNodes = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); // drop the filler row.
1203
1204         for (var i = 0; i < resourceNodes.length; ++i) {
1205             var dataGridNode = this._dataGrid.dataGridNodeFromNode(resourceNodes[i]);
1206             if (dataGridNode.isFilteredOut())
1207                 continue;
1208
1209             if (this._matchResource(dataGridNode._resource) !== -1 && dataGridNode._resource.requestId === currentMatchedRequestId)
1210                 newMatchedResourceIndex = this._matchedResources.length - 1;
1211         }
1212
1213         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedResources.length);
1214         this._highlightNthMatchedResource(newMatchedResourceIndex, !sortOrFilterApplied);
1215     },
1216
1217     jumpToPreviousSearchResult: function()
1218     {
1219         if (!this._matchedResources.length)
1220             return;
1221         this._highlightNthMatchedResource((this._currentMatchedResourceIndex + this._matchedResources.length - 1) % this._matchedResources.length, true);
1222     },
1223
1224     jumpToNextSearchResult: function()
1225     {
1226         if (!this._matchedResources.length)
1227             return;
1228         this._highlightNthMatchedResource((this._currentMatchedResourceIndex + 1) % this._matchedResources.length, true);
1229     },
1230
1231     searchCanceled: function()
1232     {
1233         this._clearSearchMatchedList();
1234         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, 0);
1235     },
1236
1237     revealAndHighlightResource: function(resource)
1238     {
1239         this._removeAllNodeHighlights();
1240
1241         var node = this._resourceGridNode(resource);
1242         if (node) {
1243             this._dataGrid.element.focus();
1244             node.reveal();
1245             this._highlightNode(node);
1246         }
1247     },
1248
1249     _removeAllNodeHighlights: function(node, decoration)
1250     {
1251         if (this._highlightedNode) {
1252             this._highlightedNode.element.removeStyleClass("highlighted-row");
1253             delete this._highlightedNode;
1254         }
1255     },
1256
1257     _highlightNode: function(node)
1258     {
1259         node.element.addStyleClass("highlighted-row");
1260         this._highlightedNode = node;
1261     }
1262 };
1263
1264 WebInspector.NetworkLogView.prototype.__proto__ = WebInspector.IFrameView.prototype;
1265
1266 WebInspector.NetworkLogView.EventTypes = {
1267     ViewCleared: "ViewCleared",
1268     RowSizeChanged: "RowSizeChanged",
1269     ResourceSelected: "ResourceSelected",
1270     SearchCountUpdated: "SearchCountUpdated",
1271     SearchIndexUpdated: "SearchIndexUpdated"
1272 };
1273
1274 WebInspector.NetworkPanel = function()
1275 {
1276     WebInspector.Panel.call(this, "network");
1277
1278     this.createSidebar();
1279     this._networkLogView = new WebInspector.NetworkLogView(this.sidebarElement);
1280     this.addChildView(this._networkLogView);
1281
1282     this._viewsContainerElement = document.createElement("div");
1283     this._viewsContainerElement.id = "network-views";
1284     this._viewsContainerElement.className = "hidden";
1285     if (!this._networkLogView.useLargeRows)
1286         this._viewsContainerElement.addStyleClass("small");
1287
1288     this.element.appendChild(this._viewsContainerElement);
1289
1290     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ViewCleared, this._onViewCleared, this);
1291     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, this._onRowSizeChanged, this);
1292     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ResourceSelected, this._onResourceSelected, this);
1293     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._onSearchCountUpdated, this);
1294     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._onSearchIndexUpdated, this);
1295
1296     this._closeButtonElement = document.createElement("button");
1297     this._closeButtonElement.id = "network-close-button";
1298     this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false);
1299     this._viewsContainerElement.appendChild(this._closeButtonElement);
1300
1301     this.registerShortcuts();
1302 }
1303
1304 WebInspector.NetworkPanel.prototype = {
1305     get toolbarItemLabel()
1306     {
1307         return WebInspector.UIString("Network");
1308     },
1309
1310     get statusBarItems()
1311     {
1312         return this._networkLogView.statusBarItems;
1313     },
1314
1315     elementsToRestoreScrollPositionsFor: function()
1316     {
1317         return this._networkLogView.elementsToRestoreScrollPositionsFor();
1318     },
1319
1320     // FIXME: only used by the layout tests, should not be exposed.
1321     _reset: function()
1322     {
1323         this._networkLogView._reset();
1324     },
1325
1326     restoreSidebarWidth: function()
1327     {
1328         if (!this._viewingResourceMode)
1329             return;
1330
1331         var preferredWidth = WebInspector.Panel.prototype.preferredSidebarWidth.call(this);
1332         if (typeof(preferredWidth) === "undefined")
1333             preferredWidth = 200;
1334         WebInspector.Panel.prototype.updateSidebarWidth.call(this, preferredWidth);
1335     },
1336
1337     updateMainViewWidth: function(width)
1338     {
1339         this._viewsContainerElement.style.left = width + "px";
1340     },
1341
1342     handleShortcut: function(event)
1343     {
1344         if (this._viewingResourceMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
1345             this._toggleGridMode();
1346             event.handled = true;
1347             return;
1348         }
1349
1350         WebInspector.Panel.prototype.handleShortcut.call(this, event);
1351     },
1352
1353     show: function()
1354     {
1355         WebInspector.Panel.prototype.show.call(this);
1356         this._networkLogView.show();
1357     },
1358
1359     get resources()
1360     {
1361         return this._networkLogView.resources;
1362     },
1363
1364     resourceById: function(id)
1365     {
1366         return this._networkLogView.resourceById(id);
1367     },
1368
1369     _resourceByAnchor: function(anchor)
1370     {
1371         var resource;
1372         if (anchor.getAttribute("request_id"))
1373             resource = this.resourceById(anchor.getAttribute("request_id"));
1374         if (!resource)
1375             resource = this._resourcesByURL[anchor.href];
1376
1377         return resource;
1378     },
1379
1380     canShowAnchorLocation: function(anchor)
1381     {
1382         return !!this._resourceByAnchor(anchor);
1383     },
1384
1385     showAnchorLocation: function(anchor)
1386     {
1387         var resource = this._resourceByAnchor(anchor);
1388         this.revealAndHighlightResource(resource)
1389     },
1390
1391     revealAndHighlightResource: function(resource)
1392     {
1393         this._toggleGridMode();
1394         if (resource)
1395             this._networkLogView.revealAndHighlightResource(resource);
1396     },
1397
1398     _onViewCleared: function(event)
1399     {
1400         this._closeVisibleResource();
1401         this._toggleGridMode();
1402         this._viewsContainerElement.removeChildren();
1403         this._viewsContainerElement.appendChild(this._closeButtonElement);
1404     },
1405
1406     _onRowSizeChanged: function(event)
1407     {
1408         if (event.data.largeRows)
1409             this._viewsContainerElement.removeStyleClass("small");
1410         else
1411             this._viewsContainerElement.addStyleClass("small");
1412     },
1413
1414     _onSearchCountUpdated: function(event)
1415     {
1416         WebInspector.searchController.updateSearchMatchesCount(event.data, this);
1417     },
1418
1419     _onSearchIndexUpdated: function(event)
1420     {
1421         WebInspector.searchController.updateCurrentMatchIndex(event.data, this);
1422     },
1423
1424     _onResourceSelected: function(event)
1425     {
1426         this._showResource(event.data);
1427     },
1428
1429     _showResource: function(resource)
1430     {
1431         if (!resource)
1432             return;
1433
1434         this._toggleViewingResourceMode();
1435
1436         if (this.visibleView) {
1437             this.removeChildView(this.visibleView);
1438             delete this.visibleView;
1439         }
1440
1441         var view = new WebInspector.NetworkItemView(resource);
1442         this.addChildView(view);
1443         view.show(this._viewsContainerElement);
1444         this.visibleView = view;
1445
1446         this.restoreSidebarWidth();
1447     },
1448
1449     _closeVisibleResource: function()
1450     {
1451         this.element.removeStyleClass("viewing-resource");
1452
1453         if (this.visibleView) {
1454             this.removeChildView(this.visibleView);
1455             delete this.visibleView;
1456         }
1457
1458         this.restoreSidebarWidth();
1459     },
1460
1461     _toggleGridMode: function()
1462     {
1463         if (this._viewingResourceMode) {
1464             this._viewingResourceMode = false;
1465             this.element.removeStyleClass("viewing-resource");
1466             this._viewsContainerElement.addStyleClass("hidden");
1467             this.sidebarElement.style.right = 0;
1468             this.sidebarElement.style.removeProperty("width");
1469         }
1470
1471         this._networkLogView.switchToDetailedView();
1472         this._networkLogView.allowPopover = true;
1473         this._networkLogView.allowResourceSelection = false;
1474     },
1475
1476     _toggleViewingResourceMode: function()
1477     {
1478         if (this._viewingResourceMode)
1479             return;
1480         this._viewingResourceMode = true;
1481
1482         this.element.addStyleClass("viewing-resource");
1483         this._viewsContainerElement.removeStyleClass("hidden");
1484         this.restoreSidebarWidth();
1485         this._networkLogView.allowPopover = false;
1486         this._networkLogView.allowResourceSelection = true;
1487         this._networkLogView.switchToBriefView();
1488     },
1489
1490     performSearch: function(searchQuery, sortOrFilterApplied)
1491     {
1492         this._networkLogView.performSearch(searchQuery, sortOrFilterApplied);
1493     },
1494
1495     jumpToPreviousSearchResult: function()
1496     {
1497         this._networkLogView.jumpToPreviousSearchResult();
1498     },
1499
1500     jumpToNextSearchResult: function()
1501     {
1502         this._networkLogView.jumpToNextSearchResult();
1503     },
1504
1505     searchCanceled: function()
1506     {
1507         this._networkLogView.searchCanceled();
1508     }
1509 }
1510
1511 WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype;
1512
1513 WebInspector.NetworkBaseCalculator = function()
1514 {
1515 }
1516
1517 WebInspector.NetworkBaseCalculator.prototype = {
1518     computeSummaryValues: function(items)
1519     {
1520         var total = 0;
1521         var categoryValues = {};
1522
1523         var itemsLength = items.length;
1524         for (var i = 0; i < itemsLength; ++i) {
1525             var item = items[i];
1526             var value = this._value(item);
1527             if (typeof value === "undefined")
1528                 continue;
1529             if (!(item.category.name in categoryValues))
1530                 categoryValues[item.category.name] = 0;
1531             categoryValues[item.category.name] += value;
1532             total += value;
1533         }
1534
1535         return {categoryValues: categoryValues, total: total};
1536     },
1537
1538     computeBarGraphPercentages: function(item)
1539     {
1540         return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100};
1541     },
1542
1543     computeBarGraphLabels: function(item)
1544     {
1545         const label = this.formatValue(this._value(item));
1546         return {left: label, right: label, tooltip: label};
1547     },
1548
1549     get boundarySpan()
1550     {
1551         return this.maximumBoundary - this.minimumBoundary;
1552     },
1553
1554     updateBoundaries: function(item)
1555     {
1556         this.minimumBoundary = 0;
1557
1558         var value = this._value(item);
1559         if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
1560             this.maximumBoundary = value;
1561             return true;
1562         }
1563         return false;
1564     },
1565
1566     reset: function()
1567     {
1568         delete this.minimumBoundary;
1569         delete this.maximumBoundary;
1570     },
1571
1572     _value: function(item)
1573     {
1574         return 0;
1575     },
1576
1577     formatValue: function(value)
1578     {
1579         return value.toString();
1580     }
1581 }
1582
1583 WebInspector.NetworkTimeCalculator = function(startAtZero)
1584 {
1585     WebInspector.NetworkBaseCalculator.call(this);
1586     this.startAtZero = startAtZero;
1587 }
1588
1589 WebInspector.NetworkTimeCalculator.prototype = {
1590     computeSummaryValues: function(resources)
1591     {
1592         var resourcesByCategory = {};
1593         var resourcesLength = resources.length;
1594         for (var i = 0; i < resourcesLength; ++i) {
1595             var resource = resources[i];
1596             if (!(resource.category.name in resourcesByCategory))
1597                 resourcesByCategory[resource.category.name] = [];
1598             resourcesByCategory[resource.category.name].push(resource);
1599         }
1600
1601         var earliestStart;
1602         var latestEnd;
1603         var categoryValues = {};
1604         for (var category in resourcesByCategory) {
1605             resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
1606             categoryValues[category] = 0;
1607
1608             var segment = {start: -1, end: -1};
1609
1610             var categoryResources = resourcesByCategory[category];
1611             var resourcesLength = categoryResources.length;
1612             for (var i = 0; i < resourcesLength; ++i) {
1613                 var resource = categoryResources[i];
1614                 if (resource.startTime === -1 || resource.endTime === -1)
1615                     continue;
1616
1617                 if (typeof earliestStart === "undefined")
1618                     earliestStart = resource.startTime;
1619                 else
1620                     earliestStart = Math.min(earliestStart, resource.startTime);
1621
1622                 if (typeof latestEnd === "undefined")
1623                     latestEnd = resource.endTime;
1624                 else
1625                     latestEnd = Math.max(latestEnd, resource.endTime);
1626
1627                 if (resource.startTime <= segment.end) {
1628                     segment.end = Math.max(segment.end, resource.endTime);
1629                     continue;
1630                 }
1631
1632                 categoryValues[category] += segment.end - segment.start;
1633
1634                 segment.start = resource.startTime;
1635                 segment.end = resource.endTime;
1636             }
1637
1638             // Add the last segment
1639             categoryValues[category] += segment.end - segment.start;
1640         }
1641
1642         return {categoryValues: categoryValues, total: latestEnd - earliestStart};
1643     },
1644
1645     computeBarGraphPercentages: function(resource)
1646     {
1647         if (resource.startTime !== -1)
1648             var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
1649         else
1650             var start = 0;
1651
1652         if (resource.responseReceivedTime !== -1)
1653             var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
1654         else
1655             var middle = (this.startAtZero ? start : 100);
1656
1657         if (resource.endTime !== -1)
1658             var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
1659         else
1660             var end = (this.startAtZero ? middle : 100);
1661
1662         if (this.startAtZero) {
1663             end -= start;
1664             middle -= start;
1665             start = 0;
1666         }
1667
1668         return {start: start, middle: middle, end: end};
1669     },
1670
1671     computePercentageFromEventTime: function(eventTime)
1672     {
1673         // This function computes a percentage in terms of the total loading time
1674         // of a specific event. If startAtZero is set, then this is useless, and we
1675         // want to return 0.
1676         if (eventTime !== -1 && !this.startAtZero)
1677             return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;
1678
1679         return 0;
1680     },
1681
1682     updateBoundariesForEventTime: function(eventTime)
1683     {
1684         if (eventTime === -1 || this.startAtZero)
1685             return false;
1686
1687         if (typeof this.maximumBoundary === "undefined" || eventTime > this.maximumBoundary) {
1688             this.maximumBoundary = eventTime;
1689             return true;
1690         }
1691         return false;
1692     },
1693
1694     computeBarGraphLabels: function(resource)
1695     {
1696         var rightLabel = "";
1697         if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
1698             rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
1699
1700         var hasLatency = resource.latency > 0;
1701         if (hasLatency)
1702             var leftLabel = this.formatValue(resource.latency);
1703         else
1704             var leftLabel = rightLabel;
1705
1706         if (resource.timing)
1707             return {left: leftLabel, right: rightLabel};
1708
1709         if (hasLatency && rightLabel) {
1710             var total = this.formatValue(resource.duration);
1711             var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
1712         } else if (hasLatency)
1713             var tooltip = WebInspector.UIString("%s latency", leftLabel);
1714         else if (rightLabel)
1715             var tooltip = WebInspector.UIString("%s download", rightLabel);
1716
1717         if (resource.cached)
1718             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1719         return {left: leftLabel, right: rightLabel, tooltip: tooltip};
1720     },
1721
1722     updateBoundaries: function(resource)
1723     {
1724         var didChange = false;
1725
1726         var lowerBound;
1727         if (this.startAtZero)
1728             lowerBound = 0;
1729         else
1730             lowerBound = this._lowerBound(resource);
1731
1732         if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
1733             this.minimumBoundary = lowerBound;
1734             didChange = true;
1735         }
1736
1737         var upperBound = this._upperBound(resource);
1738         if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
1739             this.maximumBoundary = upperBound;
1740             didChange = true;
1741         }
1742
1743         return didChange;
1744     },
1745
1746     formatValue: function(value)
1747     {
1748         return Number.secondsToString(value);
1749     },
1750
1751     _lowerBound: function(resource)
1752     {
1753         return 0;
1754     },
1755
1756     _upperBound: function(resource)
1757     {
1758         return 0;
1759     }
1760 }
1761
1762 WebInspector.NetworkTimeCalculator.prototype.__proto__ = WebInspector.NetworkBaseCalculator.prototype;
1763
1764 WebInspector.NetworkTransferTimeCalculator = function()
1765 {
1766     WebInspector.NetworkTimeCalculator.call(this, false);
1767 }
1768
1769 WebInspector.NetworkTransferTimeCalculator.prototype = {
1770     formatValue: function(value)
1771     {
1772         return Number.secondsToString(value);
1773     },
1774
1775     _lowerBound: function(resource)
1776     {
1777         return resource.startTime;
1778     },
1779
1780     _upperBound: function(resource)
1781     {
1782         return resource.endTime;
1783     }
1784 }
1785
1786 WebInspector.NetworkTransferTimeCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
1787
1788 WebInspector.NetworkTransferDurationCalculator = function()
1789 {
1790     WebInspector.NetworkTimeCalculator.call(this, true);
1791 }
1792
1793 WebInspector.NetworkTransferDurationCalculator.prototype = {
1794     formatValue: function(value)
1795     {
1796         return Number.secondsToString(value);
1797     },
1798
1799     _upperBound: function(resource)
1800     {
1801         return resource.duration;
1802     }
1803 }
1804
1805 WebInspector.NetworkTransferDurationCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
1806
1807 WebInspector.NetworkDataGridNode = function(parentView, resource)
1808 {
1809     WebInspector.DataGridNode.call(this, {});
1810     this._parentView = parentView;
1811     this._resource = resource;
1812 }
1813
1814 WebInspector.NetworkDataGridNode.prototype = {
1815     createCells: function()
1816     {
1817         // Out of sight, out of mind: create nodes offscreen to save on render tree update times when running updateOffscreenRows()
1818         this._element.addStyleClass("offscreen");
1819         this._nameCell = this._createDivInTD("name");
1820         this._methodCell = this._createDivInTD("method");
1821         this._statusCell = this._createDivInTD("status");
1822         this._typeCell = this._createDivInTD("type");
1823         if (Preferences.showNetworkPanelInitiatorColumn)
1824             this._initiatorCell = this._createDivInTD("initiator");
1825         this._sizeCell = this._createDivInTD("size");
1826         this._timeCell = this._createDivInTD("time");
1827         this._createTimelineCell();
1828         this._nameCell.addEventListener("click", this.select.bind(this), false);
1829         this._nameCell.addEventListener("dblclick", this._openInNewTab.bind(this), false);
1830     },
1831
1832     isFilteredOut: function()
1833     {
1834         if (!this._parentView._hiddenCategories.all)
1835             return false;
1836         return this._resource.category.name in this._parentView._hiddenCategories;
1837     },
1838
1839     select: function()
1840     {
1841         this._parentView.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ResourceSelected, this._resource);
1842         WebInspector.DataGridNode.prototype.select.apply(this, arguments);
1843     },
1844
1845     _highlightMatchedSubstring: function(regexp)
1846     {
1847         var domChanges = [];
1848         var matchInfo = this._nameCell.textContent.match(regexp);
1849         highlightSearchResult(this._nameCell, matchInfo.index, matchInfo[0].length, domChanges);
1850         return domChanges;
1851     },
1852
1853     _openInNewTab: function()
1854     {
1855         PageAgent.open(this._resource.url, true);
1856     },
1857
1858     get selectable()
1859     {
1860         return this._parentView.allowResourceSelection && !this.isFilteredOut();
1861     },
1862
1863     _createDivInTD: function(columnIdentifier)
1864     {
1865         var td = document.createElement("td");
1866         td.className = columnIdentifier + "-column";
1867         var div = document.createElement("div");
1868         td.appendChild(div);
1869         this._element.appendChild(td);
1870         return div;
1871     },
1872
1873     _createTimelineCell: function()
1874     {
1875         this._graphElement = document.createElement("div");
1876         this._graphElement.className = "network-graph-side";
1877
1878         this._barAreaElement = document.createElement("div");
1879         //    this._barAreaElement.className = "network-graph-bar-area hidden";
1880         this._barAreaElement.className = "network-graph-bar-area";
1881         this._barAreaElement.resource = this._resource;
1882         this._graphElement.appendChild(this._barAreaElement);
1883
1884         this._barLeftElement = document.createElement("div");
1885         this._barLeftElement.className = "network-graph-bar waiting";
1886         this._barAreaElement.appendChild(this._barLeftElement);
1887
1888         this._barRightElement = document.createElement("div");
1889         this._barRightElement.className = "network-graph-bar";
1890         this._barAreaElement.appendChild(this._barRightElement);
1891
1892
1893         this._labelLeftElement = document.createElement("div");
1894         this._labelLeftElement.className = "network-graph-label waiting";
1895         this._barAreaElement.appendChild(this._labelLeftElement);
1896
1897         this._labelRightElement = document.createElement("div");
1898         this._labelRightElement.className = "network-graph-label";
1899         this._barAreaElement.appendChild(this._labelRightElement);
1900
1901         this._graphElement.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false);
1902
1903         this._timelineCell = document.createElement("td");
1904         this._timelineCell.className = "timeline-column";
1905         this._element.appendChild(this._timelineCell);
1906         this._timelineCell.appendChild(this._graphElement);
1907     },
1908
1909     refreshResource: function()
1910     {
1911         this._refreshNameCell();
1912
1913         this._methodCell.setTextAndTitle(this._resource.requestMethod);
1914
1915         this._refreshStatusCell();
1916         this._refreshTypeCell();
1917         if (Preferences.showNetworkPanelInitiatorColumn)
1918             this._refreshInitiatorCell();
1919         this._refreshSizeCell();
1920         this._refreshTimeCell();
1921
1922         if (this._resource.cached)
1923             this._graphElement.addStyleClass("resource-cached");
1924
1925         this._element.addStyleClass("network-item");
1926         if (!this._element.hasStyleClass("network-category-" + this._resource.category.name)) {
1927             this._element.removeMatchingStyleClasses("network-category-\\w+");
1928             this._element.addStyleClass("network-category-" + this._resource.category.name);
1929         }
1930     },
1931
1932     _refreshNameCell: function()
1933     {
1934         this._nameCell.removeChildren();
1935
1936         if (this._resource.category === WebInspector.resourceCategories.images) {
1937             var previewImage = document.createElement("img");
1938             previewImage.className = "image-network-icon-preview";
1939             this._resource.populateImageSource(previewImage);
1940
1941             var iconElement = document.createElement("div");
1942             iconElement.className = "icon";
1943             iconElement.appendChild(previewImage);
1944         } else {
1945             var iconElement = document.createElement("img");
1946             iconElement.className = "icon";
1947         }
1948         this._nameCell.appendChild(iconElement);
1949         this._nameCell.appendChild(document.createTextNode(this._fileName()));
1950
1951
1952         var subtitle = this._resource.displayDomain;
1953
1954         if (this._resource.path)
1955             subtitle += this._resource.folder;
1956
1957         this._appendSubtitle(this._nameCell, subtitle);
1958         this._nameCell.title = this._resource.url;
1959     },
1960
1961     _fileName: function()
1962     {
1963         var fileName = this._resource.displayName;
1964         if (this._resource.queryString)
1965             fileName += "?" + this._resource.queryString;
1966         return fileName;
1967     },
1968
1969     _refreshStatusCell: function()
1970     {
1971         this._statusCell.removeChildren();
1972
1973         if (this._resource.failed) {
1974             if (this._resource.canceled)
1975                 this._statusCell.setTextAndTitle(WebInspector.UIString("(canceled)"));
1976             else
1977                 this._statusCell.setTextAndTitle(WebInspector.UIString("(failed)"));
1978             this._statusCell.addStyleClass("network-dim-cell");
1979             this.element.addStyleClass("network-error-row");
1980             return;
1981         }
1982
1983         this._statusCell.removeStyleClass("network-dim-cell");
1984         this.element.removeStyleClass("network-error-row");
1985
1986         if (this._resource.statusCode) {
1987             this._statusCell.appendChild(document.createTextNode(this._resource.statusCode));
1988             this._appendSubtitle(this._statusCell, this._resource.statusText);
1989             this._statusCell.title = this._resource.statusCode + " " + this._resource.statusText;
1990             if (this._resource.statusCode >= 400)
1991                 this.element.addStyleClass("network-error-row");
1992             if (this._resource.cached)
1993                 this._statusCell.addStyleClass("network-dim-cell");
1994         } else {
1995             if (!this._resource.isHttpFamily() && this._resource.finished)
1996                 this._statusCell.setTextAndTitle(WebInspector.UIString("Success"));
1997             else if (this._resource.isPingRequest())
1998                 this._statusCell.setTextAndTitle(WebInspector.UIString("(ping)"));
1999             else
2000                 this._statusCell.setTextAndTitle(WebInspector.UIString("(pending)"));
2001             this._statusCell.addStyleClass("network-dim-cell");
2002         }
2003     },
2004
2005     _refreshTypeCell: function()
2006     {
2007         if (this._resource.mimeType) {
2008             this._typeCell.removeStyleClass("network-dim-cell");
2009             this._typeCell.setTextAndTitle(this._resource.mimeType);
2010         } else if (this._resource.isPingRequest) {
2011             this._typeCell.removeStyleClass("network-dim-cell");
2012             this._typeCell.setTextAndTitle(this._resource.requestContentType());
2013         } else {
2014             this._typeCell.addStyleClass("network-dim-cell");
2015             this._typeCell.setTextAndTitle(WebInspector.UIString("Pending"));
2016         }
2017     },
2018
2019     _refreshInitiatorCell: function()
2020     {
2021         var initiator = this._resource.initiator;
2022         if ((initiator && initiator.type !== "other") || this._resource.redirectSource) {
2023             this._initiatorCell.removeStyleClass("network-dim-cell");
2024             this._initiatorCell.removeChildren();
2025             if (this._resource.redirectSource) {
2026                 var redirectSource = this._resource.redirectSource;
2027                 var anchor = WebInspector.linkifyURLAsNode(redirectSource.url, redirectSource.url, null, false);
2028                 anchor.setAttribute("request_id", redirectSource.requestId);
2029                 anchor.setAttribute("preferred_panel", "network");
2030                 this._initiatorCell.title = redirectSource.url;
2031                 this._initiatorCell.appendChild(anchor);
2032                 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Redirect"));
2033             } else if (initiator.type === "script") {
2034                 var topFrame = initiator.stackTrace[0];
2035                 // This could happen when resource loading was triggered by console.
2036                 if (!topFrame.url) {
2037                     this._initiatorCell.addStyleClass("network-dim-cell");
2038                     this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other"));
2039                     return;
2040                 }
2041                 this._initiatorCell.title = topFrame.url + ":" + topFrame.lineNumber;
2042                 var urlElement = WebInspector.debuggerPresentationModel.linkifyLocation(topFrame.url, topFrame.lineNumber - 1, 0);
2043                 this._initiatorCell.appendChild(urlElement);
2044                 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Script"));
2045             } else { // initiator.type === "parser"
2046                 this._initiatorCell.title = initiator.url + ":" + initiator.lineNumber;
2047                 this._initiatorCell.appendChild(WebInspector.linkifyResourceAsNode(initiator.url, initiator.lineNumber - 1));
2048                 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Parser"));
2049             }
2050         } else {
2051             this._initiatorCell.addStyleClass("network-dim-cell");
2052             this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other"));
2053         }
2054     },
2055
2056     _refreshSizeCell: function()
2057     {
2058         if (this._resource.cached) {
2059             this._sizeCell.setTextAndTitle(WebInspector.UIString("(from cache)"));
2060             this._sizeCell.addStyleClass("network-dim-cell");
2061         } else {
2062             var resourceSize = typeof this._resource.resourceSize === "number" ? Number.bytesToString(this._resource.resourceSize) : "?";
2063             var transferSize = typeof this._resource.transferSize === "number" ? Number.bytesToString(this._resource.transferSize) : "?";
2064             this._sizeCell.setTextAndTitle(transferSize);
2065             this._sizeCell.removeStyleClass("network-dim-cell");
2066             this._appendSubtitle(this._sizeCell, resourceSize);
2067         }
2068     },
2069
2070     _refreshTimeCell: function()
2071     {
2072         if (this._resource.duration > 0) {
2073             this._timeCell.removeStyleClass("network-dim-cell");
2074             this._timeCell.setTextAndTitle(Number.secondsToString(this._resource.duration));
2075             this._appendSubtitle(this._timeCell, Number.secondsToString(this._resource.latency));
2076         } else {
2077             this._timeCell.addStyleClass("network-dim-cell");
2078             this._timeCell.setTextAndTitle(WebInspector.UIString("Pending"));
2079         }
2080     },
2081
2082     _appendSubtitle: function(cellElement, subtitleText)
2083     {
2084         var subtitleElement = document.createElement("div");
2085         subtitleElement.className = "network-cell-subtitle";
2086         subtitleElement.textContent = subtitleText;
2087         cellElement.appendChild(subtitleElement);
2088     },
2089
2090     refreshGraph: function(calculator)
2091     {
2092         var percentages = calculator.computeBarGraphPercentages(this._resource);
2093         this._percentages = percentages;
2094
2095         this._barAreaElement.removeStyleClass("hidden");
2096
2097         if (!this._graphElement.hasStyleClass("network-category-" + this._resource.category.name)) {
2098             this._graphElement.removeMatchingStyleClasses("network-category-\\w+");
2099             this._graphElement.addStyleClass("network-category-" + this._resource.category.name);
2100         }
2101
2102         this._barLeftElement.style.setProperty("left", percentages.start + "%");
2103         this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
2104
2105         this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
2106         this._barRightElement.style.setProperty("left", percentages.middle + "%");
2107
2108         var labels = calculator.computeBarGraphLabels(this._resource);
2109         this._labelLeftElement.textContent = labels.left;
2110         this._labelRightElement.textContent = labels.right;
2111
2112         var tooltip = (labels.tooltip || "");
2113         this._barLeftElement.title = tooltip;
2114         this._labelLeftElement.title = tooltip;
2115         this._labelRightElement.title = tooltip;
2116         this._barRightElement.title = tooltip;
2117     },
2118
2119     _refreshLabelPositions: function()
2120     {
2121         if (!this._percentages)
2122             return;
2123         this._labelLeftElement.style.removeProperty("left");
2124         this._labelLeftElement.style.removeProperty("right");
2125         this._labelLeftElement.removeStyleClass("before");
2126         this._labelLeftElement.removeStyleClass("hidden");
2127
2128         this._labelRightElement.style.removeProperty("left");
2129         this._labelRightElement.style.removeProperty("right");
2130         this._labelRightElement.removeStyleClass("after");
2131         this._labelRightElement.removeStyleClass("hidden");
2132
2133         const labelPadding = 10;
2134         const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
2135         const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;
2136
2137         if (this._barLeftElement) {
2138             var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
2139             var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
2140         } else {
2141             var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
2142             var rightBarWidth = barRightElementOffsetWidth - labelPadding;
2143         }
2144
2145         const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
2146         const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;
2147
2148         const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
2149         const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
2150         const graphElementOffsetWidth = this._graphElement.offsetWidth;
2151
2152         if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
2153             var leftHidden = true;
2154
2155         if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
2156             var rightHidden = true;
2157
2158         if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
2159             // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
2160             if (labelBefore && !labelAfter)
2161                 leftHidden = true;
2162             else if (labelAfter && !labelBefore)
2163                 rightHidden = true;
2164         }
2165
2166         if (labelBefore) {
2167             if (leftHidden)
2168                 this._labelLeftElement.addStyleClass("hidden");
2169             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
2170             this._labelLeftElement.addStyleClass("before");
2171         } else {
2172             this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
2173             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
2174         }
2175
2176         if (labelAfter) {
2177             if (rightHidden)
2178                 this._labelRightElement.addStyleClass("hidden");
2179             this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
2180             this._labelRightElement.addStyleClass("after");
2181         } else {
2182             this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
2183             this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
2184         }
2185     }
2186 }
2187
2188 WebInspector.NetworkDataGridNode.NameComparator = function(a, b)
2189 {
2190     var aFileName = a._resource.displayName + (a._resource.queryString ? a._resource.queryString : "");
2191     var bFileName = b._resource.displayName + (b._resource.queryString ? b._resource.queryString : "");
2192     if (aFileName > bFileName)
2193         return 1;
2194     if (bFileName > aFileName)
2195         return -1;
2196     return 0;
2197 }
2198
2199 WebInspector.NetworkDataGridNode.SizeComparator = function(a, b)
2200 {
2201     if (b._resource.cached && !a._resource.cached)
2202         return 1;
2203     if (a._resource.cached && !b._resource.cached)
2204         return -1;
2205
2206     if (a._resource.resourceSize === b._resource.resourceSize)
2207         return 0;
2208
2209     return a._resource.resourceSize - b._resource.resourceSize;
2210 }
2211
2212 WebInspector.NetworkDataGridNode.InitiatorComparator = function(a, b)
2213 {
2214     if (!a._resource.initiator || a._resource.initiator.type === "Other")
2215         return -1;
2216     if (!b._resource.initiator || b._resource.initiator.type === "Other")
2217         return 1;
2218
2219     if (a._resource.initiator.url < b._resource.initiator.url)
2220         return -1;
2221     if (a._resource.initiator.url > b._resource.initiator.url)
2222         return 1;
2223
2224     return a._resource.initiator.lineNumber - b._resource.initiator.lineNumber;
2225 }
2226
2227 WebInspector.NetworkDataGridNode.ResourcePropertyComparator = function(propertyName, revert, a, b)
2228 {
2229     var aValue = a._resource[propertyName];
2230     var bValue = b._resource[propertyName];
2231     if (aValue > bValue)
2232         return revert ? -1 : 1;
2233     if (bValue > aValue)
2234         return revert ? 1 : -1;
2235     return 0;
2236 }
2237
2238 WebInspector.NetworkDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;