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