Web Inspector: UI performance test for network panel is incorrect. The time is includ...
[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     _defaultRefreshDelay: 500,
500
501     _scheduleRefresh: function()
502     {
503         if (this._needsRefresh)
504             return;
505
506         this._needsRefresh = true;
507
508         if (this.visible && !("_refreshTimeout" in this))
509             this._refreshTimeout = setTimeout(this.refresh.bind(this), this._defaultRefreshDelay);
510     },
511
512     _updateDividersIfNeeded: function(force)
513     {
514         if (!this._dataGrid)
515             return;
516         var timelineColumn = this._dataGrid.columns.timeline;
517         for (var i = 0; i < this._dataGrid.resizers.length; ++i) {
518             if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnID) {
519                 // Position timline grid location.
520                 this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left;
521                 this._timelineGrid.element.style.right = "18px";
522             }
523         }
524
525         var proceed = true;
526         if (!this.visible) {
527             this._scheduleRefresh();
528             proceed = false;
529         } else
530             proceed = this._timelineGrid.updateDividers(force, this.calculator);
531
532         if (!proceed)
533             return;
534
535         if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) {
536             // If our current sorting method starts at zero, that means it shows all
537             // resources starting at the same point, and so onLoad event and DOMContent
538             // event lines really wouldn't make much sense here, so don't render them.
539             // Additionally, if the calculator doesn't have the computePercentageFromEventTime
540             // function defined, we are probably sorting by size, and event times aren't relevant
541             // in this case.
542             return;
543         }
544
545         this._timelineGrid.removeEventDividers();
546         if (this._mainResourceLoadTime !== -1) {
547             var percent = this.calculator.computePercentageFromEventTime(this._mainResourceLoadTime);
548
549             var loadDivider = document.createElement("div");
550             loadDivider.className = "network-event-divider network-red-divider";
551
552             var loadDividerPadding = document.createElement("div");
553             loadDividerPadding.className = "network-event-divider-padding";
554             loadDividerPadding.title = WebInspector.UIString("Load event fired");
555             loadDividerPadding.appendChild(loadDivider);
556             loadDividerPadding.style.left = percent + "%";
557             this._timelineGrid.addEventDivider(loadDividerPadding);
558         }
559
560         if (this._mainResourceDOMContentTime !== -1) {
561             var percent = this.calculator.computePercentageFromEventTime(this._mainResourceDOMContentTime);
562
563             var domContentDivider = document.createElement("div");
564             domContentDivider.className = "network-event-divider network-blue-divider";
565
566             var domContentDividerPadding = document.createElement("div");
567             domContentDividerPadding.className = "network-event-divider-padding";
568             domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired");
569             domContentDividerPadding.appendChild(domContentDivider);
570             domContentDividerPadding.style.left = percent + "%";
571             this._timelineGrid.addEventDivider(domContentDividerPadding);
572         }
573     },
574
575     _refreshIfNeeded: function()
576     {
577         if (this._needsRefresh)
578             this.refresh();
579     },
580
581     _invalidateAllItems: function()
582     {
583         for (var i = 0; i < this._resources.length; ++i) {
584             var resource = this._resources[i];
585             this._staleResources[resource.requestId] = resource;
586         }
587     },
588
589     get calculator()
590     {
591         return this._calculator;
592     },
593
594     set calculator(x)
595     {
596         if (!x || this._calculator === x)
597             return;
598
599         this._calculator = x;
600         this._calculator.reset();
601
602         this._invalidateAllItems();
603         this.refresh();
604     },
605
606     _resourceGridNode: function(resource)
607     {
608         return this._resourceGridNodes[resource.__gridNodeId];
609     },
610
611     _createResourceGridNode: function(resource)
612     {
613         var node = new WebInspector.NetworkDataGridNode(this, resource);
614         resource.__gridNodeId = this._lastResourceGridNodeId++;
615         this._resourceGridNodes[resource.__gridNodeId] = node;
616         return node;
617     },
618
619     _createStatusbarButtons: function()
620     {
621         this._preserveLogToggle = new WebInspector.StatusBarButton(WebInspector.UIString("Preserve Log upon Navigation"), "record-profile-status-bar-item");
622         this._preserveLogToggle.addEventListener("click", this._onPreserveLogClicked.bind(this), false);
623
624         this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
625         this._clearButton.addEventListener("click", this._reset.bind(this), false);
626
627         this._largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item");
628         this._largerResourcesButton.toggled = WebInspector.settings.resourcesLargeRows.get();
629         this._largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
630     },
631
632     _onLoadEventFired: function(event)
633     {
634         this._mainResourceLoadTime = event.data || -1;
635         // Schedule refresh to update boundaries and draw the new line.
636         this._scheduleRefresh(true);
637     },
638
639     _domContentLoadedEventFired: function(event)
640     {
641         this._mainResourceDOMContentTime = event.data || -1;
642         // Schedule refresh to update boundaries and draw the new line.
643         this._scheduleRefresh(true);
644     },
645
646     wasShown: function()
647     {
648         WebInspector.IFrameView.prototype.wasShown.call(this);
649         this._refreshIfNeeded();
650     },
651
652     willHide: function()
653     {
654         WebInspector.IFrameView.prototype.willHide.call(this);
655         this._popoverHelper.hidePopover();
656     },
657
658     refresh: function()
659     {
660         this._needsRefresh = false;
661         if ("_refreshTimeout" in this) {
662             clearTimeout(this._refreshTimeout);
663             delete this._refreshTimeout;
664         }
665
666         this._removeAllNodeHighlights();
667         var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow();
668         var boundariesChanged = false;
669         if (this.calculator.updateBoundariesForEventTime) {
670             boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainResourceLoadTime) || boundariesChanged;
671             boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainResourceDOMContentTime) || boundariesChanged;
672         }
673
674         for (var resourceId in this._staleResources) {
675             var resource = this._staleResources[resourceId];
676             var node = this._resourceGridNode(resource);
677             if (!node) {
678                 // Create the timeline tree element and graph.
679                 node = this._createResourceGridNode(resource);
680                 this._dataGrid.appendChild(node);
681             }
682             node.refreshResource();
683
684             if (this.calculator.updateBoundaries(resource))
685                 boundariesChanged = true;
686
687             if (!node.isFilteredOut())
688                 this._updateHighlightIfMatched(resource);
689         }
690
691         if (boundariesChanged) {
692             // The boundaries changed, so all item graphs are stale.
693             this._invalidateAllItems();
694         }
695
696         for (var resourceId in this._staleResources)
697             this._resourceGridNode(this._staleResources[resourceId]).refreshGraph(this.calculator);
698
699         this._staleResources = {};
700         this._sortItems();
701         this._updateSummaryBar();
702         this._dataGrid.updateWidths();
703         // FIXME: evaluate performance impact of moving this before a call to sortItems()
704         if (wasScrolledToLastRow)
705             this._dataGrid.scrollToLastRow();
706     },
707
708     _onPreserveLogClicked: function(e)
709     {
710         this._preserveLogToggle.toggled = !this._preserveLogToggle.toggled;
711     },
712
713     _reset: function()
714     {
715         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ViewCleared);
716
717         this._clearSearchMatchedList();
718         if (this._popoverHelper)
719             this._popoverHelper.hidePopover();
720
721         if (this._calculator)
722             this._calculator.reset();
723
724         this._resources = [];
725         this._resourcesById = {};
726         this._resourcesByURL = {};
727         this._staleResources = {};
728         this._resourceGridNodes = {};
729
730         if (this._dataGrid) {
731             this._dataGrid.removeChildren();
732             this._updateDividersIfNeeded(true);
733             this._updateSummaryBar();
734         }
735
736         this._mainResourceLoadTime = -1;
737         this._mainResourceDOMContentTime = -1;
738
739     },
740
741     get resources()
742     {
743         return this._resources;
744     },
745
746     resourceById: function(id)
747     {
748         return this._resourcesById[id];
749     },
750
751     _onResourceStarted: function(event)
752     {
753         this._appendResource(event.data);
754     },
755
756     _appendResource: function(resource)
757     {
758         this._resources.push(resource);
759
760         // In case of redirect request id is reassigned to a redirected
761         // resource and we need to update _resourcesById ans search results.
762         if (this._resourcesById[resource.requestId]) {
763             var oldResource = resource.redirects[resource.redirects.length - 1];
764             this._resourcesById[oldResource.requestId] = oldResource;
765
766             this._updateSearchMatchedListAfterRequestIdChanged(resource.requestId, oldResource.requestId);
767         }
768         this._resourcesById[resource.requestId] = resource;
769
770         this._resourcesByURL[resource.url] = resource;
771
772         // Pull all the redirects of the main resource upon commit load.
773         if (resource.redirects) {
774             for (var i = 0; i < resource.redirects.length; ++i)
775                 this._refreshResource(resource.redirects[i]);
776         }
777
778         this._refreshResource(resource);
779     },
780
781     _onResourceUpdated: function(event)
782     {
783         this._refreshResource(event.data);
784     },
785
786     _refreshResource: function(resource)
787     {
788         this._staleResources[resource.requestId] = resource;
789         this._scheduleRefresh();
790     },
791
792     clear: function()
793     {
794         if (this._preserveLogToggle.toggled)
795             return;
796         this._reset();
797     },
798
799     _frameNavigated: function(event)
800     {
801         if (!event.data.isMainFrame)
802             return;
803
804         var loaderId = event.data.loaderId;
805         // Main frame committed load.
806         if (this._preserveLogToggle.toggled)
807             return;
808
809         // Preserve provisional load resources.
810         var resourcesToPreserve = [];
811         for (var i = 0; i < this._resources.length; ++i) {
812             var resource = this._resources[i];
813             if (resource.loaderId === loaderId)
814                 resourcesToPreserve.push(resource);
815         }
816
817         this._reset();
818
819         // Restore preserved items.
820         for (var i = 0; i < resourcesToPreserve.length; ++i)
821             this._appendResource(resourcesToPreserve[i]);
822     },
823
824     switchToDetailedView: function()
825     {
826         if (!this._dataGrid)
827             return;
828         if (this._dataGrid.selectedNode)
829             this._dataGrid.selectedNode.selected = false;
830
831         this.element.removeStyleClass("brief-mode");
832
833         this._dataGrid.showColumn("method");
834         this._dataGrid.showColumn("status");
835         this._dataGrid.showColumn("type");
836         if (Preferences.showNetworkPanelInitiatorColumn)
837             this._dataGrid.showColumn("initiator");
838         this._dataGrid.showColumn("size");
839         this._dataGrid.showColumn("time");
840         this._dataGrid.showColumn("timeline");
841
842         var widths = {};
843         widths.name = 20;
844         widths.method = 6;
845         widths.status = 6;
846         widths.type = 6;
847         if (Preferences.showNetworkPanelInitiatorColumn)
848             widths.initiator = 10;
849         widths.size = 6;
850         widths.time = 6;
851         if (Preferences.showNetworkPanelInitiatorColumn)
852             widths.timeline = 40;
853         else
854             widths.timeline = 50;
855
856         this._dataGrid.applyColumnWidthsMap(widths);
857     },
858
859     switchToBriefView: function()
860     {
861         this.element.addStyleClass("brief-mode");
862         this._removeAllNodeHighlights();
863
864         this._dataGrid.hideColumn("method");
865         this._dataGrid.hideColumn("status");
866         this._dataGrid.hideColumn("type");
867         if (Preferences.showNetworkPanelInitiatorColumn)
868             this._dataGrid.hideColumn("initiator");
869         this._dataGrid.hideColumn("size");
870         this._dataGrid.hideColumn("time");
871         this._dataGrid.hideColumn("timeline");
872
873         var widths = {};
874         widths.name = 100;
875         this._dataGrid.applyColumnWidthsMap(widths);
876
877         this._popoverHelper.hidePopover();
878     },
879
880     _toggleLargerResources: function()
881     {
882         WebInspector.settings.resourcesLargeRows.set(!WebInspector.settings.resourcesLargeRows.get());
883         this._setLargerResources(WebInspector.settings.resourcesLargeRows.get());
884     },
885
886     _setLargerResources: function(enabled)
887     {
888         this._largerResourcesButton.toggled = enabled;
889         if (!enabled) {
890             this._largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
891             this._dataGrid.element.addStyleClass("small");
892             this._timelineGrid.element.addStyleClass("small");
893         } else {
894             this._largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
895             this._dataGrid.element.removeStyleClass("small");
896             this._timelineGrid.element.removeStyleClass("small");
897         }
898         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, { largeRows: enabled });
899         this._updateOffscreenRows();
900     },
901
902     _getPopoverAnchor: function(element)
903     {
904         if (!this._allowPopover)
905             return;
906         var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label");
907         if (!anchor)
908             return null;
909         var resource = anchor.parentElement.resource;
910         return resource && resource.timing ? anchor : null;
911     },
912
913     _showPopover: function(anchor, popover)
914     {
915         var resource = anchor.parentElement.resource;
916         var tableElement = WebInspector.ResourceTimingView.createTimingTable(resource);
917         popover.show(tableElement, anchor);
918     },
919
920     _toggleGridMode: function()
921     {
922         if (this._viewingResourceMode) {
923             this._viewingResourceMode = false;
924             this.element.removeStyleClass("viewing-resource");
925             this._viewsContainerElement.addStyleClass("hidden");
926             this.sidebarElement.style.right = 0;
927             this.sidebarElement.style.removeProperty("width");
928             if (this._dataGrid.selectedNode)
929                 this._dataGrid.selectedNode.selected = false;
930         }
931
932         this._dataGrid.showColumn("method");
933         this._dataGrid.showColumn("status");
934         this._dataGrid.showColumn("type");
935         if (Preferences.showNetworkPanelInitiatorColumn)
936             this._dataGrid.showColumn("initiator");
937         this._dataGrid.showColumn("size");
938         this._dataGrid.showColumn("time");
939
940         var widths = {};
941         widths.name = 20;
942         widths.method = 6;
943         widths.status = 6;
944         widths.type = 6;
945         if (Preferences.showNetworkPanelInitiatorColumn)
946             widths.initiator = 10;
947         widths.size = 6;
948         widths.time = 6;
949         if (Preferences.showNetworkPanelInitiatorColumn)
950             widths.timeline = 40;
951         else
952             widths.timeline = 50;
953
954         this._dataGrid.showColumn("timeline");
955         this._dataGrid.applyColumnWidthsMap(widths);
956     },
957
958     _toggleViewingResourceMode: function()
959     {
960         if (this._viewingResourceMode)
961             return;
962         this._viewingResourceMode = true;
963
964         this.element.addStyleClass("viewing-resource");
965
966         this._dataGrid.hideColumn("method");
967         this._dataGrid.hideColumn("status");
968         this._dataGrid.hideColumn("type");
969         if (Preferences.showNetworkPanelInitiatorColumn)
970             this._dataGrid.hideColumn("initiator");
971         this._dataGrid.hideColumn("size");
972         this._dataGrid.hideColumn("time");
973         this._dataGrid.hideColumn("timeline");
974
975         this._viewsContainerElement.removeStyleClass("hidden");
976         this.restoreSidebarWidth();
977
978         var widths = {};
979         widths.name = 100;
980         this._dataGrid.applyColumnWidthsMap(widths);
981     },
982
983     _contextMenu: function(event)
984     {
985         var contextMenu = new WebInspector.ContextMenu();
986         var gridNode = this._dataGrid.dataGridNodeFromNode(event.target);
987         var resource = gridNode && gridNode._resource;
988
989         if (resource) {
990             contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, resource.url, false));
991             contextMenu.appendSeparator();
992             contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), this._copyLocation.bind(this, resource));
993             if (resource.requestHeadersText)
994                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy request headers" : "Copy Request Headers"), this._copyRequestHeaders.bind(this, resource));
995             if (resource.responseHeadersText)
996                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy response headers" : "Copy Response Headers"), this._copyResponseHeaders.bind(this, resource));
997             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy entry as HAR" : "Copy Entry as HAR"), this._copyResource.bind(this, resource));
998         }
999         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy all as HAR" : "Copy All as HAR"), this._copyAll.bind(this));
1000
1001         if (Preferences.saveAsAvailable) {
1002             contextMenu.appendSeparator();
1003             if (resource)
1004                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save entry as HAR" : "Save Entry as HAR"), this._exportResource.bind(this, resource));
1005             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save all as HAR" : "Save All as HAR"), this._exportAll.bind(this));
1006         }
1007
1008         if (Preferences.canClearCacheAndCookies) {
1009             contextMenu.appendSeparator();
1010             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cache" : "Clear Browser Cache"), this._clearBrowserCache.bind(this));
1011             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cookies" : "Clear Browser Cookies"), this._clearBrowserCookies.bind(this));
1012         }
1013
1014         contextMenu.show(event);
1015     },
1016
1017     _copyAll: function()
1018     {
1019         var harArchive = {
1020             log: (new WebInspector.HARLog(this._resources)).build()
1021         };
1022         InspectorFrontendHost.copyText(JSON.stringify(harArchive));
1023     },
1024
1025     _copyResource: function(resource)
1026     {
1027         var har = (new WebInspector.HAREntry(resource)).build();
1028         InspectorFrontendHost.copyText(JSON.stringify(har));
1029     },
1030
1031     _copyLocation: function(resource)
1032     {
1033         InspectorFrontendHost.copyText(resource.url);
1034     },
1035
1036     _copyRequestHeaders: function(resource)
1037     {
1038         InspectorFrontendHost.copyText(resource.requestHeadersText);
1039     },
1040
1041     _copyResponseHeaders: function(resource)
1042     {
1043         InspectorFrontendHost.copyText(resource.responseHeadersText);
1044     },
1045
1046     _exportAll: function()
1047     {
1048         var harArchive = {
1049             log: (new WebInspector.HARLog(this._resources)).build()
1050         };
1051         InspectorFrontendHost.saveAs(WebInspector.mainResource.domain + ".har", JSON.stringify(harArchive));
1052     },
1053
1054     _exportResource: function(resource)
1055     {
1056         var har = (new WebInspector.HAREntry(resource)).build();
1057         InspectorFrontendHost.saveAs(resource.displayName + ".har", JSON.stringify(har));
1058     },
1059
1060     _clearBrowserCache: function(event)
1061     {
1062         if (confirm(WebInspector.UIString("Are you sure you want to clear browser cache?")))
1063             NetworkAgent.clearBrowserCache();
1064     },
1065
1066     _clearBrowserCookies: function(event)
1067     {
1068         if (confirm(WebInspector.UIString("Are you sure you want to clear browser cookies?")))
1069             NetworkAgent.clearBrowserCookies();
1070     },
1071
1072     _updateOffscreenRows: function(e)
1073     {
1074         var dataTableBody = this._dataGrid.dataTableBody;
1075         var rows = dataTableBody.children;
1076         var recordsCount = rows.length;
1077         if (recordsCount < 2)
1078             return;  // Filler row only.
1079
1080         var visibleTop = this._dataGrid.scrollContainer.scrollTop;
1081         var visibleBottom = visibleTop + this._dataGrid.scrollContainer.offsetHeight;
1082
1083         var rowHeight = 0;
1084
1085         // Filler is at recordsCount - 1.
1086         var unfilteredRowIndex = 0;
1087         for (var i = 0; i < recordsCount - 1; ++i) {
1088             var row = rows[i];
1089
1090             var dataGridNode = this._dataGrid.dataGridNodeFromNode(row);
1091             if (dataGridNode.isFilteredOut()) {
1092                 row.removeStyleClass("offscreen");
1093                 continue;
1094             }
1095
1096             if (!rowHeight)
1097                 rowHeight = row.offsetHeight;
1098
1099             var rowIsVisible = unfilteredRowIndex * rowHeight < visibleBottom && (unfilteredRowIndex + 1) * rowHeight > visibleTop;
1100             if (rowIsVisible !== row.rowIsVisible) {
1101                 if (rowIsVisible)
1102                     row.removeStyleClass("offscreen");
1103                 else
1104                     row.addStyleClass("offscreen");
1105                 row.rowIsVisible = rowIsVisible;
1106             }
1107             unfilteredRowIndex++;
1108         }
1109     },
1110
1111     _matchResource: function(resource)
1112     {
1113         if (!this._searchRegExp)
1114             return -1;
1115
1116         if ((!resource.displayName || !resource.displayName.match(this._searchRegExp)) && !resource.folder.match(this._searchRegExp))
1117             return -1;
1118
1119         if (resource.requestId in this._matchedResourcesMap)
1120             return this._matchedResourcesMap[resource.requestId];
1121
1122         var matchedResourceIndex = this._matchedResources.length;
1123         this._matchedResourcesMap[resource.requestId] = matchedResourceIndex;
1124         this._matchedResources.push(resource.requestId);
1125
1126         return matchedResourceIndex;
1127     },
1128
1129     _clearSearchMatchedList: function()
1130     {
1131         this._matchedResources = [];
1132         this._matchedResourcesMap = {};
1133         this._highlightNthMatchedResource(-1, false);
1134     },
1135
1136     _updateSearchMatchedListAfterRequestIdChanged: function(oldRequestId, newRequestId)
1137     {
1138         var resourceIndex = this._matchedResourcesMap[oldRequestId];
1139         if (resourceIndex) {
1140             delete this._matchedResourcesMap[oldRequestId];
1141             this._matchedResourcesMap[newRequestId] = resourceIndex;
1142             this._matchedResources[resourceIndex] = newRequestId;
1143         }
1144     },
1145
1146     _updateHighlightIfMatched: function(resource)
1147     {
1148         var matchedResourceIndex = this._matchResource(resource);
1149         if (matchedResourceIndex === -1)
1150             return;
1151
1152         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedResources.length);
1153
1154         if (this._currentMatchedResourceIndex !== -1 && this._currentMatchedResourceIndex !== matchedResourceIndex)
1155             return;
1156
1157         this._highlightNthMatchedResource(matchedResourceIndex, false);
1158     },
1159
1160     _highlightNthMatchedResource: function(matchedResourceIndex, reveal)
1161     {
1162         if (this._highlightedSubstringChanges) {
1163             revertDomChanges(this._highlightedSubstringChanges);
1164             this._highlightedSubstringChanges = null;
1165         }
1166
1167         if (matchedResourceIndex === -1) {
1168             this._currentMatchedResourceIndex = matchedResourceIndex;
1169             return;
1170         }
1171
1172         var resource = this._resourcesById[this._matchedResources[matchedResourceIndex]];
1173         if (!resource)
1174             return;
1175
1176         var nameMatched = resource.displayName && resource.displayName.match(this._searchRegExp);
1177         var pathMatched = resource.path && resource.folder.match(this._searchRegExp);
1178         if (!nameMatched && pathMatched && !this._largerResourcesButton.toggled)
1179             this._toggleLargerResources();
1180
1181         var node = this._resourceGridNode(resource);
1182         if (node) {
1183             this._highlightedSubstringChanges = node._highlightMatchedSubstring(this._searchRegExp);
1184             if (reveal)
1185                 node.reveal();
1186             this._currentMatchedResourceIndex = matchedResourceIndex;
1187         }
1188         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._currentMatchedResourceIndex + 1);
1189     },
1190
1191     performSearch: function(searchQuery, sortOrFilterApplied)
1192     {
1193         var newMatchedResourceIndex = 0;
1194         var currentMatchedRequestId;
1195         if (this._currentMatchedResourceIndex !== -1)
1196             currentMatchedRequestId = this._matchedResources[this._currentMatchedResourceIndex];
1197
1198         if (!sortOrFilterApplied)
1199             this._searchRegExp = createSearchRegex(searchQuery);
1200
1201         this._clearSearchMatchedList();
1202
1203         var childNodes = this._dataGrid.dataTableBody.childNodes;
1204         var resourceNodes = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); // drop the filler row.
1205
1206         for (var i = 0; i < resourceNodes.length; ++i) {
1207             var dataGridNode = this._dataGrid.dataGridNodeFromNode(resourceNodes[i]);
1208             if (dataGridNode.isFilteredOut())
1209                 continue;
1210
1211             if (this._matchResource(dataGridNode._resource) !== -1 && dataGridNode._resource.requestId === currentMatchedRequestId)
1212                 newMatchedResourceIndex = this._matchedResources.length - 1;
1213         }
1214
1215         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedResources.length);
1216         this._highlightNthMatchedResource(newMatchedResourceIndex, !sortOrFilterApplied);
1217     },
1218
1219     jumpToPreviousSearchResult: function()
1220     {
1221         if (!this._matchedResources.length)
1222             return;
1223         this._highlightNthMatchedResource((this._currentMatchedResourceIndex + this._matchedResources.length - 1) % this._matchedResources.length, true);
1224     },
1225
1226     jumpToNextSearchResult: function()
1227     {
1228         if (!this._matchedResources.length)
1229             return;
1230         this._highlightNthMatchedResource((this._currentMatchedResourceIndex + 1) % this._matchedResources.length, true);
1231     },
1232
1233     searchCanceled: function()
1234     {
1235         this._clearSearchMatchedList();
1236         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, 0);
1237     },
1238
1239     revealAndHighlightResource: function(resource)
1240     {
1241         this._removeAllNodeHighlights();
1242
1243         var node = this._resourceGridNode(resource);
1244         if (node) {
1245             this._dataGrid.element.focus();
1246             node.reveal();
1247             this._highlightNode(node);
1248         }
1249     },
1250
1251     _removeAllNodeHighlights: function(node, decoration)
1252     {
1253         if (this._highlightedNode) {
1254             this._highlightedNode.element.removeStyleClass("highlighted-row");
1255             delete this._highlightedNode;
1256         }
1257     },
1258
1259     _highlightNode: function(node)
1260     {
1261         node.element.addStyleClass("highlighted-row");
1262         this._highlightedNode = node;
1263     }
1264 };
1265
1266 WebInspector.NetworkLogView.prototype.__proto__ = WebInspector.IFrameView.prototype;
1267
1268 WebInspector.NetworkLogView.EventTypes = {
1269     ViewCleared: "ViewCleared",
1270     RowSizeChanged: "RowSizeChanged",
1271     ResourceSelected: "ResourceSelected",
1272     SearchCountUpdated: "SearchCountUpdated",
1273     SearchIndexUpdated: "SearchIndexUpdated"
1274 };
1275
1276 WebInspector.NetworkPanel = function()
1277 {
1278     WebInspector.Panel.call(this, "network");
1279
1280     this.createSidebar();
1281     this._networkLogView = new WebInspector.NetworkLogView(this.sidebarElement);
1282     this.addChildView(this._networkLogView);
1283
1284     this._viewsContainerElement = document.createElement("div");
1285     this._viewsContainerElement.id = "network-views";
1286     this._viewsContainerElement.className = "hidden";
1287     if (!this._networkLogView.useLargeRows)
1288         this._viewsContainerElement.addStyleClass("small");
1289
1290     this.element.appendChild(this._viewsContainerElement);
1291
1292     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ViewCleared, this._onViewCleared, this);
1293     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, this._onRowSizeChanged, this);
1294     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ResourceSelected, this._onResourceSelected, this);
1295     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._onSearchCountUpdated, this);
1296     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._onSearchIndexUpdated, this);
1297
1298     this._closeButtonElement = document.createElement("button");
1299     this._closeButtonElement.id = "network-close-button";
1300     this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false);
1301     this._viewsContainerElement.appendChild(this._closeButtonElement);
1302
1303     this.registerShortcuts();
1304 }
1305
1306 WebInspector.NetworkPanel.prototype = {
1307     get toolbarItemLabel()
1308     {
1309         return WebInspector.UIString("Network");
1310     },
1311
1312     get statusBarItems()
1313     {
1314         return this._networkLogView.statusBarItems;
1315     },
1316
1317     elementsToRestoreScrollPositionsFor: function()
1318     {
1319         return this._networkLogView.elementsToRestoreScrollPositionsFor();
1320     },
1321
1322     // FIXME: only used by the layout tests, should not be exposed.
1323     _reset: function()
1324     {
1325         this._networkLogView._reset();
1326     },
1327
1328     restoreSidebarWidth: function()
1329     {
1330         if (!this._viewingResourceMode)
1331             return;
1332
1333         var preferredWidth = WebInspector.Panel.prototype.preferredSidebarWidth.call(this);
1334         if (typeof(preferredWidth) === "undefined")
1335             preferredWidth = 200;
1336         WebInspector.Panel.prototype.updateSidebarWidth.call(this, preferredWidth);
1337     },
1338
1339     updateMainViewWidth: function(width)
1340     {
1341         this._viewsContainerElement.style.left = width + "px";
1342     },
1343
1344     handleShortcut: function(event)
1345     {
1346         if (this._viewingResourceMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
1347             this._toggleGridMode();
1348             event.handled = true;
1349             return;
1350         }
1351
1352         WebInspector.Panel.prototype.handleShortcut.call(this, event);
1353     },
1354
1355     show: function()
1356     {
1357         WebInspector.Panel.prototype.show.call(this);
1358         this._networkLogView.show();
1359     },
1360
1361     get resources()
1362     {
1363         return this._networkLogView.resources;
1364     },
1365
1366     resourceById: function(id)
1367     {
1368         return this._networkLogView.resourceById(id);
1369     },
1370
1371     _resourceByAnchor: function(anchor)
1372     {
1373         var resource;
1374         if (anchor.getAttribute("request_id"))
1375             resource = this.resourceById(anchor.getAttribute("request_id"));
1376         if (!resource)
1377             resource = this._resourcesByURL[anchor.href];
1378
1379         return resource;
1380     },
1381
1382     canShowAnchorLocation: function(anchor)
1383     {
1384         return !!this._resourceByAnchor(anchor);
1385     },
1386
1387     showAnchorLocation: function(anchor)
1388     {
1389         var resource = this._resourceByAnchor(anchor);
1390         this.revealAndHighlightResource(resource)
1391     },
1392
1393     revealAndHighlightResource: function(resource)
1394     {
1395         this._toggleGridMode();
1396         if (resource)
1397             this._networkLogView.revealAndHighlightResource(resource);
1398     },
1399
1400     _onViewCleared: function(event)
1401     {
1402         this._closeVisibleResource();
1403         this._toggleGridMode();
1404         this._viewsContainerElement.removeChildren();
1405         this._viewsContainerElement.appendChild(this._closeButtonElement);
1406     },
1407
1408     _onRowSizeChanged: function(event)
1409     {
1410         if (event.data.largeRows)
1411             this._viewsContainerElement.removeStyleClass("small");
1412         else
1413             this._viewsContainerElement.addStyleClass("small");
1414     },
1415
1416     _onSearchCountUpdated: function(event)
1417     {
1418         WebInspector.searchController.updateSearchMatchesCount(event.data, this);
1419     },
1420
1421     _onSearchIndexUpdated: function(event)
1422     {
1423         WebInspector.searchController.updateCurrentMatchIndex(event.data, this);
1424     },
1425
1426     _onResourceSelected: function(event)
1427     {
1428         this._showResource(event.data);
1429     },
1430
1431     _showResource: function(resource)
1432     {
1433         if (!resource)
1434             return;
1435
1436         this._toggleViewingResourceMode();
1437
1438         if (this.visibleView) {
1439             this.removeChildView(this.visibleView);
1440             delete this.visibleView;
1441         }
1442
1443         var view = new WebInspector.NetworkItemView(resource);
1444         this.addChildView(view);
1445         view.show(this._viewsContainerElement);
1446         this.visibleView = view;
1447
1448         this.restoreSidebarWidth();
1449     },
1450
1451     _closeVisibleResource: function()
1452     {
1453         this.element.removeStyleClass("viewing-resource");
1454
1455         if (this.visibleView) {
1456             this.removeChildView(this.visibleView);
1457             delete this.visibleView;
1458         }
1459
1460         this.restoreSidebarWidth();
1461     },
1462
1463     _toggleGridMode: function()
1464     {
1465         if (this._viewingResourceMode) {
1466             this._viewingResourceMode = false;
1467             this.element.removeStyleClass("viewing-resource");
1468             this._viewsContainerElement.addStyleClass("hidden");
1469             this.sidebarElement.style.right = 0;
1470             this.sidebarElement.style.removeProperty("width");
1471         }
1472
1473         this._networkLogView.switchToDetailedView();
1474         this._networkLogView.allowPopover = true;
1475         this._networkLogView.allowResourceSelection = false;
1476     },
1477
1478     _toggleViewingResourceMode: function()
1479     {
1480         if (this._viewingResourceMode)
1481             return;
1482         this._viewingResourceMode = true;
1483
1484         this.element.addStyleClass("viewing-resource");
1485         this._viewsContainerElement.removeStyleClass("hidden");
1486         this.restoreSidebarWidth();
1487         this._networkLogView.allowPopover = false;
1488         this._networkLogView.allowResourceSelection = true;
1489         this._networkLogView.switchToBriefView();
1490     },
1491
1492     performSearch: function(searchQuery, sortOrFilterApplied)
1493     {
1494         this._networkLogView.performSearch(searchQuery, sortOrFilterApplied);
1495     },
1496
1497     jumpToPreviousSearchResult: function()
1498     {
1499         this._networkLogView.jumpToPreviousSearchResult();
1500     },
1501
1502     jumpToNextSearchResult: function()
1503     {
1504         this._networkLogView.jumpToNextSearchResult();
1505     },
1506
1507     searchCanceled: function()
1508     {
1509         this._networkLogView.searchCanceled();
1510     }
1511 }
1512
1513 WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype;
1514
1515 WebInspector.NetworkBaseCalculator = function()
1516 {
1517 }
1518
1519 WebInspector.NetworkBaseCalculator.prototype = {
1520     computeSummaryValues: function(items)
1521     {
1522         var total = 0;
1523         var categoryValues = {};
1524
1525         var itemsLength = items.length;
1526         for (var i = 0; i < itemsLength; ++i) {
1527             var item = items[i];
1528             var value = this._value(item);
1529             if (typeof value === "undefined")
1530                 continue;
1531             if (!(item.category.name in categoryValues))
1532                 categoryValues[item.category.name] = 0;
1533             categoryValues[item.category.name] += value;
1534             total += value;
1535         }
1536
1537         return {categoryValues: categoryValues, total: total};
1538     },
1539
1540     computeBarGraphPercentages: function(item)
1541     {
1542         return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100};
1543     },
1544
1545     computeBarGraphLabels: function(item)
1546     {
1547         const label = this.formatValue(this._value(item));
1548         return {left: label, right: label, tooltip: label};
1549     },
1550
1551     get boundarySpan()
1552     {
1553         return this.maximumBoundary - this.minimumBoundary;
1554     },
1555
1556     updateBoundaries: function(item)
1557     {
1558         this.minimumBoundary = 0;
1559
1560         var value = this._value(item);
1561         if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
1562             this.maximumBoundary = value;
1563             return true;
1564         }
1565         return false;
1566     },
1567
1568     reset: function()
1569     {
1570         delete this.minimumBoundary;
1571         delete this.maximumBoundary;
1572     },
1573
1574     _value: function(item)
1575     {
1576         return 0;
1577     },
1578
1579     formatValue: function(value)
1580     {
1581         return value.toString();
1582     }
1583 }
1584
1585 WebInspector.NetworkTimeCalculator = function(startAtZero)
1586 {
1587     WebInspector.NetworkBaseCalculator.call(this);
1588     this.startAtZero = startAtZero;
1589 }
1590
1591 WebInspector.NetworkTimeCalculator.prototype = {
1592     computeSummaryValues: function(resources)
1593     {
1594         var resourcesByCategory = {};
1595         var resourcesLength = resources.length;
1596         for (var i = 0; i < resourcesLength; ++i) {
1597             var resource = resources[i];
1598             if (!(resource.category.name in resourcesByCategory))
1599                 resourcesByCategory[resource.category.name] = [];
1600             resourcesByCategory[resource.category.name].push(resource);
1601         }
1602
1603         var earliestStart;
1604         var latestEnd;
1605         var categoryValues = {};
1606         for (var category in resourcesByCategory) {
1607             resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
1608             categoryValues[category] = 0;
1609
1610             var segment = {start: -1, end: -1};
1611
1612             var categoryResources = resourcesByCategory[category];
1613             var resourcesLength = categoryResources.length;
1614             for (var i = 0; i < resourcesLength; ++i) {
1615                 var resource = categoryResources[i];
1616                 if (resource.startTime === -1 || resource.endTime === -1)
1617                     continue;
1618
1619                 if (typeof earliestStart === "undefined")
1620                     earliestStart = resource.startTime;
1621                 else
1622                     earliestStart = Math.min(earliestStart, resource.startTime);
1623
1624                 if (typeof latestEnd === "undefined")
1625                     latestEnd = resource.endTime;
1626                 else
1627                     latestEnd = Math.max(latestEnd, resource.endTime);
1628
1629                 if (resource.startTime <= segment.end) {
1630                     segment.end = Math.max(segment.end, resource.endTime);
1631                     continue;
1632                 }
1633
1634                 categoryValues[category] += segment.end - segment.start;
1635
1636                 segment.start = resource.startTime;
1637                 segment.end = resource.endTime;
1638             }
1639
1640             // Add the last segment
1641             categoryValues[category] += segment.end - segment.start;
1642         }
1643
1644         return {categoryValues: categoryValues, total: latestEnd - earliestStart};
1645     },
1646
1647     computeBarGraphPercentages: function(resource)
1648     {
1649         if (resource.startTime !== -1)
1650             var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
1651         else
1652             var start = 0;
1653
1654         if (resource.responseReceivedTime !== -1)
1655             var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
1656         else
1657             var middle = (this.startAtZero ? start : 100);
1658
1659         if (resource.endTime !== -1)
1660             var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
1661         else
1662             var end = (this.startAtZero ? middle : 100);
1663
1664         if (this.startAtZero) {
1665             end -= start;
1666             middle -= start;
1667             start = 0;
1668         }
1669
1670         return {start: start, middle: middle, end: end};
1671     },
1672
1673     computePercentageFromEventTime: function(eventTime)
1674     {
1675         // This function computes a percentage in terms of the total loading time
1676         // of a specific event. If startAtZero is set, then this is useless, and we
1677         // want to return 0.
1678         if (eventTime !== -1 && !this.startAtZero)
1679             return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;
1680
1681         return 0;
1682     },
1683
1684     updateBoundariesForEventTime: function(eventTime)
1685     {
1686         if (eventTime === -1 || this.startAtZero)
1687             return false;
1688
1689         if (typeof this.maximumBoundary === "undefined" || eventTime > this.maximumBoundary) {
1690             this.maximumBoundary = eventTime;
1691             return true;
1692         }
1693         return false;
1694     },
1695
1696     computeBarGraphLabels: function(resource)
1697     {
1698         var rightLabel = "";
1699         if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
1700             rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
1701
1702         var hasLatency = resource.latency > 0;
1703         if (hasLatency)
1704             var leftLabel = this.formatValue(resource.latency);
1705         else
1706             var leftLabel = rightLabel;
1707
1708         if (resource.timing)
1709             return {left: leftLabel, right: rightLabel};
1710
1711         if (hasLatency && rightLabel) {
1712             var total = this.formatValue(resource.duration);
1713             var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
1714         } else if (hasLatency)
1715             var tooltip = WebInspector.UIString("%s latency", leftLabel);
1716         else if (rightLabel)
1717             var tooltip = WebInspector.UIString("%s download", rightLabel);
1718
1719         if (resource.cached)
1720             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1721         return {left: leftLabel, right: rightLabel, tooltip: tooltip};
1722     },
1723
1724     updateBoundaries: function(resource)
1725     {
1726         var didChange = false;
1727
1728         var lowerBound;
1729         if (this.startAtZero)
1730             lowerBound = 0;
1731         else
1732             lowerBound = this._lowerBound(resource);
1733
1734         if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
1735             this.minimumBoundary = lowerBound;
1736             didChange = true;
1737         }
1738
1739         var upperBound = this._upperBound(resource);
1740         if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
1741             this.maximumBoundary = upperBound;
1742             didChange = true;
1743         }
1744
1745         return didChange;
1746     },
1747
1748     formatValue: function(value)
1749     {
1750         return Number.secondsToString(value);
1751     },
1752
1753     _lowerBound: function(resource)
1754     {
1755         return 0;
1756     },
1757
1758     _upperBound: function(resource)
1759     {
1760         return 0;
1761     }
1762 }
1763
1764 WebInspector.NetworkTimeCalculator.prototype.__proto__ = WebInspector.NetworkBaseCalculator.prototype;
1765
1766 WebInspector.NetworkTransferTimeCalculator = function()
1767 {
1768     WebInspector.NetworkTimeCalculator.call(this, false);
1769 }
1770
1771 WebInspector.NetworkTransferTimeCalculator.prototype = {
1772     formatValue: function(value)
1773     {
1774         return Number.secondsToString(value);
1775     },
1776
1777     _lowerBound: function(resource)
1778     {
1779         return resource.startTime;
1780     },
1781
1782     _upperBound: function(resource)
1783     {
1784         return resource.endTime;
1785     }
1786 }
1787
1788 WebInspector.NetworkTransferTimeCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
1789
1790 WebInspector.NetworkTransferDurationCalculator = function()
1791 {
1792     WebInspector.NetworkTimeCalculator.call(this, true);
1793 }
1794
1795 WebInspector.NetworkTransferDurationCalculator.prototype = {
1796     formatValue: function(value)
1797     {
1798         return Number.secondsToString(value);
1799     },
1800
1801     _upperBound: function(resource)
1802     {
1803         return resource.duration;
1804     }
1805 }
1806
1807 WebInspector.NetworkTransferDurationCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
1808
1809 WebInspector.NetworkDataGridNode = function(parentView, resource)
1810 {
1811     WebInspector.DataGridNode.call(this, {});
1812     this._parentView = parentView;
1813     this._resource = resource;
1814 }
1815
1816 WebInspector.NetworkDataGridNode.prototype = {
1817     createCells: function()
1818     {
1819         // Out of sight, out of mind: create nodes offscreen to save on render tree update times when running updateOffscreenRows()
1820         this._element.addStyleClass("offscreen");
1821         this._nameCell = this._createDivInTD("name");
1822         this._methodCell = this._createDivInTD("method");
1823         this._statusCell = this._createDivInTD("status");
1824         this._typeCell = this._createDivInTD("type");
1825         if (Preferences.showNetworkPanelInitiatorColumn)
1826             this._initiatorCell = this._createDivInTD("initiator");
1827         this._sizeCell = this._createDivInTD("size");
1828         this._timeCell = this._createDivInTD("time");
1829         this._createTimelineCell();
1830         this._nameCell.addEventListener("click", this.select.bind(this), false);
1831         this._nameCell.addEventListener("dblclick", this._openInNewTab.bind(this), false);
1832     },
1833
1834     isFilteredOut: function()
1835     {
1836         if (!this._parentView._hiddenCategories.all)
1837             return false;
1838         return this._resource.category.name in this._parentView._hiddenCategories;
1839     },
1840
1841     select: function()
1842     {
1843         this._parentView.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ResourceSelected, this._resource);
1844         WebInspector.DataGridNode.prototype.select.apply(this, arguments);
1845     },
1846
1847     _highlightMatchedSubstring: function(regexp)
1848     {
1849         var domChanges = [];
1850         var matchInfo = this._nameCell.textContent.match(regexp);
1851         highlightSearchResult(this._nameCell, matchInfo.index, matchInfo[0].length, domChanges);
1852         return domChanges;
1853     },
1854
1855     _openInNewTab: function()
1856     {
1857         PageAgent.open(this._resource.url, true);
1858     },
1859
1860     get selectable()
1861     {
1862         return this._parentView.allowResourceSelection && !this.isFilteredOut();
1863     },
1864
1865     _createDivInTD: function(columnIdentifier)
1866     {
1867         var td = document.createElement("td");
1868         td.className = columnIdentifier + "-column";
1869         var div = document.createElement("div");
1870         td.appendChild(div);
1871         this._element.appendChild(td);
1872         return div;
1873     },
1874
1875     _createTimelineCell: function()
1876     {
1877         this._graphElement = document.createElement("div");
1878         this._graphElement.className = "network-graph-side";
1879
1880         this._barAreaElement = document.createElement("div");
1881         //    this._barAreaElement.className = "network-graph-bar-area hidden";
1882         this._barAreaElement.className = "network-graph-bar-area";
1883         this._barAreaElement.resource = this._resource;
1884         this._graphElement.appendChild(this._barAreaElement);
1885
1886         this._barLeftElement = document.createElement("div");
1887         this._barLeftElement.className = "network-graph-bar waiting";
1888         this._barAreaElement.appendChild(this._barLeftElement);
1889
1890         this._barRightElement = document.createElement("div");
1891         this._barRightElement.className = "network-graph-bar";
1892         this._barAreaElement.appendChild(this._barRightElement);
1893
1894
1895         this._labelLeftElement = document.createElement("div");
1896         this._labelLeftElement.className = "network-graph-label waiting";
1897         this._barAreaElement.appendChild(this._labelLeftElement);
1898
1899         this._labelRightElement = document.createElement("div");
1900         this._labelRightElement.className = "network-graph-label";
1901         this._barAreaElement.appendChild(this._labelRightElement);
1902
1903         this._graphElement.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false);
1904
1905         this._timelineCell = document.createElement("td");
1906         this._timelineCell.className = "timeline-column";
1907         this._element.appendChild(this._timelineCell);
1908         this._timelineCell.appendChild(this._graphElement);
1909     },
1910
1911     refreshResource: function()
1912     {
1913         this._refreshNameCell();
1914
1915         this._methodCell.setTextAndTitle(this._resource.requestMethod);
1916
1917         this._refreshStatusCell();
1918         this._refreshTypeCell();
1919         if (Preferences.showNetworkPanelInitiatorColumn)
1920             this._refreshInitiatorCell();
1921         this._refreshSizeCell();
1922         this._refreshTimeCell();
1923
1924         if (this._resource.cached)
1925             this._graphElement.addStyleClass("resource-cached");
1926
1927         this._element.addStyleClass("network-item");
1928         if (!this._element.hasStyleClass("network-category-" + this._resource.category.name)) {
1929             this._element.removeMatchingStyleClasses("network-category-\\w+");
1930             this._element.addStyleClass("network-category-" + this._resource.category.name);
1931         }
1932     },
1933
1934     _refreshNameCell: function()
1935     {
1936         this._nameCell.removeChildren();
1937
1938         if (this._resource.category === WebInspector.resourceCategories.images) {
1939             var previewImage = document.createElement("img");
1940             previewImage.className = "image-network-icon-preview";
1941             this._resource.populateImageSource(previewImage);
1942
1943             var iconElement = document.createElement("div");
1944             iconElement.className = "icon";
1945             iconElement.appendChild(previewImage);
1946         } else {
1947             var iconElement = document.createElement("img");
1948             iconElement.className = "icon";
1949         }
1950         this._nameCell.appendChild(iconElement);
1951         this._nameCell.appendChild(document.createTextNode(this._fileName()));
1952
1953
1954         var subtitle = this._resource.displayDomain;
1955
1956         if (this._resource.path)
1957             subtitle += this._resource.folder;
1958
1959         this._appendSubtitle(this._nameCell, subtitle);
1960         this._nameCell.title = this._resource.url;
1961     },
1962
1963     _fileName: function()
1964     {
1965         var fileName = this._resource.displayName;
1966         if (this._resource.queryString)
1967             fileName += "?" + this._resource.queryString;
1968         return fileName;
1969     },
1970
1971     _refreshStatusCell: function()
1972     {
1973         this._statusCell.removeChildren();
1974
1975         if (this._resource.failed) {
1976             if (this._resource.canceled)
1977                 this._statusCell.setTextAndTitle(WebInspector.UIString("(canceled)"));
1978             else
1979                 this._statusCell.setTextAndTitle(WebInspector.UIString("(failed)"));
1980             this._statusCell.addStyleClass("network-dim-cell");
1981             this.element.addStyleClass("network-error-row");
1982             return;
1983         }
1984
1985         this._statusCell.removeStyleClass("network-dim-cell");
1986         this.element.removeStyleClass("network-error-row");
1987
1988         if (this._resource.statusCode) {
1989             this._statusCell.appendChild(document.createTextNode(this._resource.statusCode));
1990             this._appendSubtitle(this._statusCell, this._resource.statusText);
1991             this._statusCell.title = this._resource.statusCode + " " + this._resource.statusText;
1992             if (this._resource.statusCode >= 400)
1993                 this.element.addStyleClass("network-error-row");
1994             if (this._resource.cached)
1995                 this._statusCell.addStyleClass("network-dim-cell");
1996         } else {
1997             if (!this._resource.isHttpFamily() && this._resource.finished)
1998                 this._statusCell.setTextAndTitle(WebInspector.UIString("Success"));
1999             else if (this._resource.isPingRequest())
2000                 this._statusCell.setTextAndTitle(WebInspector.UIString("(ping)"));
2001             else
2002                 this._statusCell.setTextAndTitle(WebInspector.UIString("(pending)"));
2003             this._statusCell.addStyleClass("network-dim-cell");
2004         }
2005     },
2006
2007     _refreshTypeCell: function()
2008     {
2009         if (this._resource.mimeType) {
2010             this._typeCell.removeStyleClass("network-dim-cell");
2011             this._typeCell.setTextAndTitle(this._resource.mimeType);
2012         } else if (this._resource.isPingRequest) {
2013             this._typeCell.removeStyleClass("network-dim-cell");
2014             this._typeCell.setTextAndTitle(this._resource.requestContentType());
2015         } else {
2016             this._typeCell.addStyleClass("network-dim-cell");
2017             this._typeCell.setTextAndTitle(WebInspector.UIString("Pending"));
2018         }
2019     },
2020
2021     _refreshInitiatorCell: function()
2022     {
2023         var initiator = this._resource.initiator;
2024         if ((initiator && initiator.type !== "other") || this._resource.redirectSource) {
2025             this._initiatorCell.removeStyleClass("network-dim-cell");
2026             this._initiatorCell.removeChildren();
2027             if (this._resource.redirectSource) {
2028                 var redirectSource = this._resource.redirectSource;
2029                 var anchor = WebInspector.linkifyURLAsNode(redirectSource.url, redirectSource.url, null, false);
2030                 anchor.setAttribute("request_id", redirectSource.requestId);
2031                 anchor.setAttribute("preferred_panel", "network");
2032                 this._initiatorCell.title = redirectSource.url;
2033                 this._initiatorCell.appendChild(anchor);
2034                 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Redirect"));
2035             } else if (initiator.type === "script") {
2036                 var topFrame = initiator.stackTrace[0];
2037                 // This could happen when resource loading was triggered by console.
2038                 if (!topFrame.url) {
2039                     this._initiatorCell.addStyleClass("network-dim-cell");
2040                     this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other"));
2041                     return;
2042                 }
2043                 this._initiatorCell.title = topFrame.url + ":" + topFrame.lineNumber;
2044                 var urlElement = WebInspector.debuggerPresentationModel.linkifyLocation(topFrame.url, topFrame.lineNumber - 1, 0);
2045                 this._initiatorCell.appendChild(urlElement);
2046                 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Script"));
2047             } else { // initiator.type === "parser"
2048                 this._initiatorCell.title = initiator.url + ":" + initiator.lineNumber;
2049                 this._initiatorCell.appendChild(WebInspector.linkifyResourceAsNode(initiator.url, initiator.lineNumber - 1));
2050                 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Parser"));
2051             }
2052         } else {
2053             this._initiatorCell.addStyleClass("network-dim-cell");
2054             this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other"));
2055         }
2056     },
2057
2058     _refreshSizeCell: function()
2059     {
2060         if (this._resource.cached) {
2061             this._sizeCell.setTextAndTitle(WebInspector.UIString("(from cache)"));
2062             this._sizeCell.addStyleClass("network-dim-cell");
2063         } else {
2064             var resourceSize = typeof this._resource.resourceSize === "number" ? Number.bytesToString(this._resource.resourceSize) : "?";
2065             var transferSize = typeof this._resource.transferSize === "number" ? Number.bytesToString(this._resource.transferSize) : "?";
2066             this._sizeCell.setTextAndTitle(transferSize);
2067             this._sizeCell.removeStyleClass("network-dim-cell");
2068             this._appendSubtitle(this._sizeCell, resourceSize);
2069         }
2070     },
2071
2072     _refreshTimeCell: function()
2073     {
2074         if (this._resource.duration > 0) {
2075             this._timeCell.removeStyleClass("network-dim-cell");
2076             this._timeCell.setTextAndTitle(Number.secondsToString(this._resource.duration));
2077             this._appendSubtitle(this._timeCell, Number.secondsToString(this._resource.latency));
2078         } else {
2079             this._timeCell.addStyleClass("network-dim-cell");
2080             this._timeCell.setTextAndTitle(WebInspector.UIString("Pending"));
2081         }
2082     },
2083
2084     _appendSubtitle: function(cellElement, subtitleText)
2085     {
2086         var subtitleElement = document.createElement("div");
2087         subtitleElement.className = "network-cell-subtitle";
2088         subtitleElement.textContent = subtitleText;
2089         cellElement.appendChild(subtitleElement);
2090     },
2091
2092     refreshGraph: function(calculator)
2093     {
2094         var percentages = calculator.computeBarGraphPercentages(this._resource);
2095         this._percentages = percentages;
2096
2097         this._barAreaElement.removeStyleClass("hidden");
2098
2099         if (!this._graphElement.hasStyleClass("network-category-" + this._resource.category.name)) {
2100             this._graphElement.removeMatchingStyleClasses("network-category-\\w+");
2101             this._graphElement.addStyleClass("network-category-" + this._resource.category.name);
2102         }
2103
2104         this._barLeftElement.style.setProperty("left", percentages.start + "%");
2105         this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
2106
2107         this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
2108         this._barRightElement.style.setProperty("left", percentages.middle + "%");
2109
2110         var labels = calculator.computeBarGraphLabels(this._resource);
2111         this._labelLeftElement.textContent = labels.left;
2112         this._labelRightElement.textContent = labels.right;
2113
2114         var tooltip = (labels.tooltip || "");
2115         this._barLeftElement.title = tooltip;
2116         this._labelLeftElement.title = tooltip;
2117         this._labelRightElement.title = tooltip;
2118         this._barRightElement.title = tooltip;
2119     },
2120
2121     _refreshLabelPositions: function()
2122     {
2123         if (!this._percentages)
2124             return;
2125         this._labelLeftElement.style.removeProperty("left");
2126         this._labelLeftElement.style.removeProperty("right");
2127         this._labelLeftElement.removeStyleClass("before");
2128         this._labelLeftElement.removeStyleClass("hidden");
2129
2130         this._labelRightElement.style.removeProperty("left");
2131         this._labelRightElement.style.removeProperty("right");
2132         this._labelRightElement.removeStyleClass("after");
2133         this._labelRightElement.removeStyleClass("hidden");
2134
2135         const labelPadding = 10;
2136         const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
2137         const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;
2138
2139         if (this._barLeftElement) {
2140             var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
2141             var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
2142         } else {
2143             var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
2144             var rightBarWidth = barRightElementOffsetWidth - labelPadding;
2145         }
2146
2147         const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
2148         const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;
2149
2150         const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
2151         const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
2152         const graphElementOffsetWidth = this._graphElement.offsetWidth;
2153
2154         if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
2155             var leftHidden = true;
2156
2157         if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
2158             var rightHidden = true;
2159
2160         if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
2161             // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
2162             if (labelBefore && !labelAfter)
2163                 leftHidden = true;
2164             else if (labelAfter && !labelBefore)
2165                 rightHidden = true;
2166         }
2167
2168         if (labelBefore) {
2169             if (leftHidden)
2170                 this._labelLeftElement.addStyleClass("hidden");
2171             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
2172             this._labelLeftElement.addStyleClass("before");
2173         } else {
2174             this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
2175             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
2176         }
2177
2178         if (labelAfter) {
2179             if (rightHidden)
2180                 this._labelRightElement.addStyleClass("hidden");
2181             this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
2182             this._labelRightElement.addStyleClass("after");
2183         } else {
2184             this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
2185             this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
2186         }
2187     }
2188 }
2189
2190 WebInspector.NetworkDataGridNode.NameComparator = function(a, b)
2191 {
2192     var aFileName = a._resource.displayName + (a._resource.queryString ? a._resource.queryString : "");
2193     var bFileName = b._resource.displayName + (b._resource.queryString ? b._resource.queryString : "");
2194     if (aFileName > bFileName)
2195         return 1;
2196     if (bFileName > aFileName)
2197         return -1;
2198     return 0;
2199 }
2200
2201 WebInspector.NetworkDataGridNode.SizeComparator = function(a, b)
2202 {
2203     if (b._resource.cached && !a._resource.cached)
2204         return 1;
2205     if (a._resource.cached && !b._resource.cached)
2206         return -1;
2207
2208     if (a._resource.resourceSize === b._resource.resourceSize)
2209         return 0;
2210
2211     return a._resource.resourceSize - b._resource.resourceSize;
2212 }
2213
2214 WebInspector.NetworkDataGridNode.InitiatorComparator = function(a, b)
2215 {
2216     if (!a._resource.initiator || a._resource.initiator.type === "Other")
2217         return -1;
2218     if (!b._resource.initiator || b._resource.initiator.type === "Other")
2219         return 1;
2220
2221     if (a._resource.initiator.url < b._resource.initiator.url)
2222         return -1;
2223     if (a._resource.initiator.url > b._resource.initiator.url)
2224         return 1;
2225
2226     return a._resource.initiator.lineNumber - b._resource.initiator.lineNumber;
2227 }
2228
2229 WebInspector.NetworkDataGridNode.ResourcePropertyComparator = function(propertyName, revert, a, b)
2230 {
2231     var aValue = a._resource[propertyName];
2232     var bValue = b._resource[propertyName];
2233     if (aValue > bValue)
2234         return revert ? -1 : 1;
2235     if (bValue > aValue)
2236         return revert ? 1 : -1;
2237     return 0;
2238 }
2239
2240 WebInspector.NetworkDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;