Upstream version 5.34.92.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / 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 importScript("RequestView.js");
32 importScript("NetworkItemView.js");
33 importScript("RequestCookiesView.js");
34 importScript("RequestHeadersView.js");
35 importScript("RequestHTMLView.js");
36 importScript("RequestJSONView.js");
37 importScript("RequestPreviewView.js");
38 importScript("RequestResponseView.js");
39 importScript("RequestTimingView.js");
40 importScript("ResourceWebSocketFrameView.js");
41
42 /**
43  * @constructor
44  * @implements {WebInspector.Searchable}
45  * @extends {WebInspector.View}
46  * @param {!WebInspector.FilterBar} filterBar
47  * @param {!WebInspector.Setting} coulmnsVisibilitySetting
48  */
49 WebInspector.NetworkLogView = function(filterBar, coulmnsVisibilitySetting)
50 {
51     WebInspector.View.call(this);
52     this.registerRequiredCSS("networkLogView.css");
53     this.registerRequiredCSS("filter.css");
54
55     this._filterBar = filterBar;
56     this._coulmnsVisibilitySetting = coulmnsVisibilitySetting;
57     this._allowRequestSelection = false;
58     this._requests = [];
59     this._requestsById = {};
60     this._requestsByURL = {};
61     this._staleRequests = {};
62     this._requestGridNodes = {};
63     this._lastRequestGridNodeId = 0;
64     this._mainRequestLoadTime = -1;
65     this._mainRequestDOMContentLoadedTime = -1;
66     this._matchedRequests = [];
67     this._highlightedSubstringChanges = [];
68     this._filteredOutRequests = new Map();
69
70     this._matchedRequestsMap = {};
71     this._currentMatchedRequestIndex = -1;
72
73     this._createStatusbarButtons();
74     this._createStatusBarItems();
75     this._linkifier = new WebInspector.Linkifier();
76
77     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestStarted, this._onRequestStarted, this);
78     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestUpdated, this._onRequestUpdated, this);
79     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestUpdated, this);
80
81     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.WillReloadPage, this._willReloadPage, this);
82     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this);
83     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.Load, this._loadEventFired, this);
84     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, this._domContentLoadedEventFired, this);
85
86     this._addFilters();
87     this._initializeView();
88     this._recordButton.toggled = true;
89     WebInspector.networkLog.requests.forEach(this._appendRequest.bind(this));
90 }
91
92 WebInspector.NetworkLogView.HTTPSchemas = {"http": true, "https": true, "ws": true, "wss": true};
93 WebInspector.NetworkLogView._responseHeaderColumns = ["Cache-Control", "Connection", "Content-Encoding", "Content-Length", "ETag", "Keep-Alive", "Last-Modified", "Server", "Vary"];
94 WebInspector.NetworkLogView._defaultColumnsVisibility = {
95     method: true, status: true, scheme: false, domain: false, type: true, initiator: true, cookies: false, setCookies: false, size: true, time: true,
96     "Cache-Control": false, "Connection": false, "Content-Encoding": false, "Content-Length": false, "ETag": false, "Keep-Alive": false, "Last-Modified": false, "Server": false, "Vary": false
97 };
98 WebInspector.NetworkLogView._defaultRefreshDelay = 500;
99
100 WebInspector.NetworkLogView.prototype = {
101     _addFilters: function()
102     {
103         this._textFilterUI = new WebInspector.TextFilterUI();
104         this._textFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._filterChanged, this);
105         this._filterBar.addFilter(this._textFilterUI);
106
107         this._resourceTypeFilterUI = new WebInspector.NamedBitSetFilterUI();
108         for (var typeId in WebInspector.resourceTypes) {
109             var resourceType = WebInspector.resourceTypes[typeId];
110             this._resourceTypeFilterUI.addBit(resourceType.name(), resourceType.categoryTitle());
111         }
112         this._resourceTypeFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
113         this._filterBar.addFilter(this._resourceTypeFilterUI);
114
115         var dataURLSetting = WebInspector.settings.networkHideDataURL;
116         this._dataURLFilterUI = new WebInspector.CheckboxFilterUI("hide-data-url", WebInspector.UIString("Hide data URLs"), true, dataURLSetting);
117         this._dataURLFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
118         this._filterBar.addFilter(this._dataURLFilterUI);
119     },
120
121     _filterChanged: function(event)
122     {
123         this._removeAllNodeHighlights();
124         this.searchCanceled();
125         this._filterRequests();
126     },
127
128     _initializeView: function()
129     {
130         this.element.id = "network-container";
131
132         this._createSortingFunctions();
133         this._createTable();
134         this._createTimelineGrid();
135         this._summaryBarElement = this.element.createChild("div", "network-summary-bar");
136
137         if (!this.useLargeRows)
138             this._setLargerRequests(this.useLargeRows);
139
140         this._allowPopover = true;
141         this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), this._onHidePopover.bind(this));
142         // Enable faster hint.
143         this._popoverHelper.setTimeout(100);
144
145         this.calculator = new WebInspector.NetworkTransferTimeCalculator();
146
147         this.switchToDetailedView();
148     },
149
150     get statusBarItems()
151     {
152         return [this._recordButton.element, this._clearButton.element, this._filterBar.filterButton().element, this._largerRequestsButton.element, this._preserveLogCheckbox.element, this._progressBarContainer];
153     },
154
155     get useLargeRows()
156     {
157         return WebInspector.settings.resourcesLargeRows.get();
158     },
159
160     set allowPopover(flag)
161     {
162         this._allowPopover = flag;
163     },
164
165     /**
166      * @return {!Array.<!Element>}
167      */
168     elementsToRestoreScrollPositionsFor: function()
169     {
170         if (!this._dataGrid) // Not initialized yet.
171             return [];
172         return [this._dataGrid.scrollContainer];
173     },
174
175     onResize: function()
176     {
177         this._updateOffscreenRows();
178     },
179
180     _createTimelineGrid: function()
181     {
182         this._timelineGrid = new WebInspector.TimelineGrid();
183         this._timelineGrid.element.classList.add("network-timeline-grid");
184         this._dataGrid.element.appendChild(this._timelineGrid.element);
185     },
186
187     _createTable: function()
188     {
189         var columns = [];
190         columns.push({
191             id: "name",
192             titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path")),
193             title: WebInspector.UIString("Name"),
194             sortable: true,
195             weight: 20,
196             disclosure: true
197         });
198
199         columns.push({
200             id: "method",
201             title: WebInspector.UIString("Method"),
202             sortable: true,
203             weight: 6
204         });
205
206         columns.push({
207             id: "status",
208             titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text")),
209             title: WebInspector.UIString("Status"),
210             sortable: true,
211             weight: 6
212         });
213
214         columns.push({
215             id: "scheme",
216             title: WebInspector.UIString("Scheme"),
217             sortable: true,
218             weight: 6
219         });
220
221         columns.push({
222             id: "domain",
223             title: WebInspector.UIString("Domain"),
224             sortable: true,
225             weight: 6
226         });
227
228         columns.push({
229             id: "type",
230             title: WebInspector.UIString("Type"),
231             sortable: true,
232             weight: 6
233         });
234
235         columns.push({
236             id: "initiator",
237             title: WebInspector.UIString("Initiator"),
238             sortable: true,
239             weight: 10
240         });
241
242         columns.push({
243             id: "cookies",
244             title: WebInspector.UIString("Cookies"),
245             sortable: true,
246             weight: 6,
247             align: WebInspector.DataGrid.Align.Right
248         });
249
250         columns.push({
251             id: "setCookies",
252             title: WebInspector.UIString("Set-Cookies"),
253             sortable: true,
254             weight: 6,
255             align: WebInspector.DataGrid.Align.Right
256         });
257
258         columns.push({
259             id: "size",
260             titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Content")),
261             title: WebInspector.UIString("Size"),
262             sortable: true,
263             weight: 6,
264             align: WebInspector.DataGrid.Align.Right
265         });
266
267         columns.push({
268             id: "time",
269             titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Latency")),
270             title: WebInspector.UIString("Time"),
271             sortable: true,
272             weight: 6,
273             align: WebInspector.DataGrid.Align.Right
274         });
275
276         var responseHeaderColumns = WebInspector.NetworkLogView._responseHeaderColumns;
277         for (var i = 0; i < responseHeaderColumns.length; ++i) {
278             var headerName = responseHeaderColumns[i];
279             var descriptor = {
280                 id: headerName,
281                 title: WebInspector.UIString(headerName),
282                 weight: 6
283             }
284             if (headerName === "Content-Length")
285                 descriptor.align = WebInspector.DataGrid.Align.Right;
286             columns.push(descriptor);
287         }
288
289         columns.push({
290             id: "timeline",
291             titleDOMFragment: document.createDocumentFragment(),
292             title: WebInspector.UIString("Timeline"),
293             sortable: false,
294             weight: 40,
295             sort: WebInspector.DataGrid.Order.Ascending
296         });
297
298         this._dataGrid = new WebInspector.DataGrid(columns);
299         this._dataGrid.setName("networkLog");
300         this._dataGrid.resizeMethod = WebInspector.DataGrid.ResizeMethod.Last;
301         this._dataGrid.element.classList.add("network-log-grid");
302         this._dataGrid.element.addEventListener("contextmenu", this._contextMenu.bind(this), true);
303         this._dataGrid.show(this.element);
304
305         // Event listeners need to be added _after_ we attach to the document, so that owner document is properly update.
306         this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortItems, this);
307         this._dataGrid.addEventListener(WebInspector.DataGrid.Events.ColumnsResized, this._updateDividersIfNeeded, this);
308         this._dataGrid.scrollContainer.addEventListener("scroll", this._updateOffscreenRows.bind(this));
309
310         this._patchTimelineHeader();
311     },
312
313     _makeHeaderFragment: function(title, subtitle)
314     {
315         var fragment = document.createDocumentFragment();
316         fragment.createTextChild(title);
317         var subtitleDiv = fragment.createChild("div", "network-header-subtitle");
318         subtitleDiv.createTextChild(subtitle);
319         return fragment;
320     },
321
322     _patchTimelineHeader: function()
323     {
324         var timelineSorting = document.createElement("select");
325
326         var option = document.createElement("option");
327         option.value = "startTime";
328         option.label = WebInspector.UIString("Timeline");
329         timelineSorting.appendChild(option);
330
331         option = document.createElement("option");
332         option.value = "startTime";
333         option.label = WebInspector.UIString("Start Time");
334         timelineSorting.appendChild(option);
335
336         option = document.createElement("option");
337         option.value = "responseTime";
338         option.label = WebInspector.UIString("Response Time");
339         timelineSorting.appendChild(option);
340
341         option = document.createElement("option");
342         option.value = "endTime";
343         option.label = WebInspector.UIString("End Time");
344         timelineSorting.appendChild(option);
345
346         option = document.createElement("option");
347         option.value = "duration";
348         option.label = WebInspector.UIString("Duration");
349         timelineSorting.appendChild(option);
350
351         option = document.createElement("option");
352         option.value = "latency";
353         option.label = WebInspector.UIString("Latency");
354         timelineSorting.appendChild(option);
355
356         var header = this._dataGrid.headerTableHeader("timeline");
357         header.replaceChild(timelineSorting, header.firstChild);
358
359         timelineSorting.addEventListener("click", function(event) { event.consume() }, false);
360         timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false);
361         this._timelineSortSelector = timelineSorting;
362     },
363
364     _createSortingFunctions: function()
365     {
366         this._sortingFunctions = {};
367         this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator;
368         this._sortingFunctions.method = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "method", false);
369         this._sortingFunctions.status = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "statusCode", false);
370         this._sortingFunctions.scheme = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "scheme", false);
371         this._sortingFunctions.domain = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "domain", false);
372         this._sortingFunctions.type = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "mimeType", false);
373         this._sortingFunctions.initiator = WebInspector.NetworkDataGridNode.InitiatorComparator;
374         this._sortingFunctions.cookies = WebInspector.NetworkDataGridNode.RequestCookiesCountComparator;
375         this._sortingFunctions.setCookies = WebInspector.NetworkDataGridNode.ResponseCookiesCountComparator;
376         this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator;
377         this._sortingFunctions.time = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "duration", false);
378         this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "startTime", false);
379         this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "startTime", false);
380         this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "endTime", false);
381         this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "responseReceivedTime", false);
382         this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "duration", true);
383         this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "latency", true);
384
385         var timeCalculator = new WebInspector.NetworkTransferTimeCalculator();
386         var durationCalculator = new WebInspector.NetworkTransferDurationCalculator();
387
388         this._calculators = {};
389         this._calculators.timeline = timeCalculator;
390         this._calculators.startTime = timeCalculator;
391         this._calculators.endTime = timeCalculator;
392         this._calculators.responseTime = timeCalculator;
393         this._calculators.duration = durationCalculator;
394         this._calculators.latency = durationCalculator;
395     },
396
397     _sortItems: function()
398     {
399         this._removeAllNodeHighlights();
400         var columnIdentifier = this._dataGrid.sortColumnIdentifier();
401         if (columnIdentifier === "timeline") {
402             this._sortByTimeline();
403             return;
404         }
405         var sortingFunction = this._sortingFunctions[columnIdentifier];
406         if (!sortingFunction)
407             return;
408
409         this._dataGrid.sortNodes(sortingFunction, !this._dataGrid.isSortOrderAscending());
410         this._timelineSortSelector.selectedIndex = 0;
411         this._updateOffscreenRows();
412
413         this.searchCanceled();
414
415         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
416             action: WebInspector.UserMetrics.UserActionNames.NetworkSort,
417             column: columnIdentifier,
418             sortOrder: this._dataGrid.sortOrder()
419         });
420     },
421
422     _sortByTimeline: function()
423     {
424         this._removeAllNodeHighlights();
425         var selectedIndex = this._timelineSortSelector.selectedIndex;
426         if (!selectedIndex)
427             selectedIndex = 1; // Sort by start time by default.
428         var selectedOption = this._timelineSortSelector[selectedIndex];
429         var value = selectedOption.value;
430
431         var sortingFunction = this._sortingFunctions[value];
432         this._dataGrid.sortNodes(sortingFunction);
433         this.calculator = this._calculators[value];
434         if (this.calculator.startAtZero)
435             this._timelineGrid.hideEventDividers();
436         else
437             this._timelineGrid.showEventDividers();
438         this._dataGrid.markColumnAsSortedBy("timeline", WebInspector.DataGrid.Order.Ascending);
439         this._updateOffscreenRows();
440     },
441
442     _createStatusBarItems: function()
443     {
444         this._progressBarContainer = document.createElement("div");
445         this._progressBarContainer.className = "status-bar-item";
446     },
447
448     _updateSummaryBar: function()
449     {
450         var requestsNumber = this._requests.length;
451
452         if (!requestsNumber) {
453             if (this._summaryBarElement._isDisplayingWarning)
454                 return;
455             this._summaryBarElement._isDisplayingWarning = true;
456             this._summaryBarElement.removeChildren();
457             this._summaryBarElement.createChild("div", "warning-icon-small");
458             var text = WebInspector.UIString("No requests captured. Reload the page to see detailed information on the network activity.");
459             this._summaryBarElement.appendChild(document.createTextNode(text));
460             this._summaryBarElement.title = text;
461             return;
462         }
463         delete this._summaryBarElement._isDisplayingWarning;
464
465         var transferSize = 0;
466         var selectedRequestsNumber = 0;
467         var selectedTransferSize = 0;
468         var baseTime = -1;
469         var maxTime = -1;
470         for (var i = 0; i < this._requests.length; ++i) {
471             var request = this._requests[i];
472             var requestTransferSize = request.transferSize;
473             transferSize += requestTransferSize;
474             if (!this._filteredOutRequests.get(request)) {
475                 selectedRequestsNumber++;
476                 selectedTransferSize += requestTransferSize;
477             }
478             if (request.url === WebInspector.inspectedPageURL)
479                 baseTime = request.startTime;
480             if (request.endTime > maxTime)
481                 maxTime = request.endTime;
482         }
483         var text = "";
484         if (selectedRequestsNumber !== requestsNumber) {
485             text += String.sprintf(WebInspector.UIString("%d / %d requests"), selectedRequestsNumber, requestsNumber);
486             text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s / %s transferred"), Number.bytesToString(selectedTransferSize), Number.bytesToString(transferSize));
487         } else {
488             text += String.sprintf(WebInspector.UIString("%d requests"), requestsNumber);
489             text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize));
490         }
491         if (baseTime !== -1 && this._mainRequestLoadTime !== -1 && this._mainRequestDOMContentLoadedTime !== -1 && this._mainRequestDOMContentLoadedTime > baseTime) {
492             text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s (load: %s, DOMContentLoaded: %s)"),
493                         Number.secondsToString(maxTime - baseTime),
494                         Number.secondsToString(this._mainRequestLoadTime - baseTime),
495                         Number.secondsToString(this._mainRequestDOMContentLoadedTime - baseTime));
496         }
497         this._summaryBarElement.textContent = text;
498         this._summaryBarElement.title = text;
499     },
500
501     _scheduleRefresh: function()
502     {
503         if (this._needsRefresh)
504             return;
505
506         this._needsRefresh = true;
507
508         if (this.isShowing() && !this._refreshTimeout)
509             this._refreshTimeout = setTimeout(this.refresh.bind(this), WebInspector.NetworkLogView._defaultRefreshDelay);
510     },
511
512     _updateDividersIfNeeded: function()
513     {
514         if (!this._dataGrid)
515             return;
516         var timelineColumn = this._dataGrid.columns.timeline;
517         for (var i = 0; i < this._dataGrid.resizers.length; ++i) {
518             if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnIndex) {
519                 // Position timline grid location.
520                 this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left;
521             }
522         }
523
524         var proceed = true;
525         if (!this.isShowing()) {
526             this._scheduleRefresh();
527             proceed = false;
528         } else {
529             this.calculator.setDisplayWindow(this._timelineGrid.dividersElement.clientWidth);
530             proceed = this._timelineGrid.updateDividers(this.calculator);
531         }
532         if (!proceed)
533             return;
534
535         if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) {
536             // If our current sorting method starts at zero, that means it shows all
537             // requests starting at the same point, and so onLoad event and DOMContent
538             // event lines really wouldn't make much sense here, so don't render them.
539             // Additionally, if the calculator doesn't have the computePercentageFromEventTime
540             // function defined, we are probably sorting by size, and event times aren't relevant
541             // in this case.
542             return;
543         }
544
545         this._timelineGrid.removeEventDividers();
546         if (this._mainRequestLoadTime !== -1) {
547             var percent = this.calculator.computePercentageFromEventTime(this._mainRequestLoadTime);
548
549             var loadDivider = document.createElement("div");
550             loadDivider.className = "network-event-divider network-red-divider";
551
552             var loadDividerPadding = document.createElement("div");
553             loadDividerPadding.className = "network-event-divider-padding";
554             loadDividerPadding.title = WebInspector.UIString("Load event fired");
555             loadDividerPadding.appendChild(loadDivider);
556             loadDividerPadding.style.left = percent + "%";
557             this._timelineGrid.addEventDivider(loadDividerPadding);
558         }
559
560         if (this._mainRequestDOMContentLoadedTime !== -1) {
561             var percent = this.calculator.computePercentageFromEventTime(this._mainRequestDOMContentLoadedTime);
562
563             var domContentLoadedDivider = document.createElement("div");
564             domContentLoadedDivider.className = "network-event-divider network-blue-divider";
565
566             var domContentLoadedDividerPadding = document.createElement("div");
567             domContentLoadedDividerPadding.className = "network-event-divider-padding";
568             domContentLoadedDividerPadding.title = WebInspector.UIString("DOMContentLoaded event fired");
569             domContentLoadedDividerPadding.appendChild(domContentLoadedDivider);
570             domContentLoadedDividerPadding.style.left = percent + "%";
571             this._timelineGrid.addEventDivider(domContentLoadedDividerPadding);
572         }
573     },
574
575     _refreshIfNeeded: function()
576     {
577         if (this._needsRefresh)
578             this.refresh();
579     },
580
581     _invalidateAllItems: function()
582     {
583         for (var i = 0; i < this._requests.length; ++i) {
584             var request = this._requests[i];
585             this._staleRequests[request.requestId] = request;
586         }
587     },
588
589     get calculator()
590     {
591         return this._calculator;
592     },
593
594     set calculator(x)
595     {
596         if (!x || this._calculator === x)
597             return;
598
599         this._calculator = x;
600         this._calculator.reset();
601
602         this._invalidateAllItems();
603         this.refresh();
604     },
605
606     _requestGridNode: function(request)
607     {
608         return this._requestGridNodes[request.__gridNodeId];
609     },
610
611     _createRequestGridNode: function(request)
612     {
613         var node = new WebInspector.NetworkDataGridNode(this, request);
614         request.__gridNodeId = this._lastRequestGridNodeId++;
615         this._requestGridNodes[request.__gridNodeId] = node;
616         return node;
617     },
618
619     _createStatusbarButtons: function()
620     {
621         this._recordButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record Network Log"), "record-profile-status-bar-item");
622         this._recordButton.addEventListener("click", this._onRecordButtonClicked, this);
623
624         this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
625         this._clearButton.addEventListener("click", this._reset, this);
626
627         this._largerRequestsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item");
628         this._largerRequestsButton.toggled = WebInspector.settings.resourcesLargeRows.get();
629         this._largerRequestsButton.addEventListener("click", this._toggleLargerRequests, this);
630
631         this._preserveLogCheckbox = new WebInspector.StatusBarCheckbox(WebInspector.UIString("Preserve log"));
632     },
633
634     _loadEventFired: function(event)
635     {
636         if (!this._recordButton.toggled)
637             return;
638
639         this._mainRequestLoadTime = event.data || -1;
640         // Schedule refresh to update boundaries and draw the new line.
641         this._scheduleRefresh();
642     },
643
644     _domContentLoadedEventFired: function(event)
645     {
646         if (!this._recordButton.toggled)
647             return;
648         this._mainRequestDOMContentLoadedTime = event.data || -1;
649         // Schedule refresh to update boundaries and draw the new line.
650         this._scheduleRefresh();
651     },
652
653     wasShown: function()
654     {
655         this._refreshIfNeeded();
656     },
657
658     willHide: function()
659     {
660         this._popoverHelper.hidePopover();
661     },
662
663     refresh: function()
664     {
665         this._needsRefresh = false;
666         if (this._refreshTimeout) {
667             clearTimeout(this._refreshTimeout);
668             delete this._refreshTimeout;
669         }
670
671         this._removeAllNodeHighlights();
672         var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow();
673         var boundariesChanged = false;
674         if (this.calculator.updateBoundariesForEventTime) {
675             boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainRequestLoadTime) || boundariesChanged;
676             boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime) || boundariesChanged;
677         }
678
679         for (var requestId in this._staleRequests) {
680             var request = this._staleRequests[requestId];
681             var node = this._requestGridNode(request);
682             if (!node) {
683                 // Create the timeline tree element and graph.
684                 node = this._createRequestGridNode(request);
685                 this._dataGrid.rootNode().appendChild(node);
686             }
687             node.refreshRequest();
688             this._applyFilter(node);
689
690             if (this.calculator.updateBoundaries(request))
691                 boundariesChanged = true;
692
693             if (!node.isFilteredOut())
694                 this._updateHighlightIfMatched(request);
695         }
696
697         if (boundariesChanged) {
698             // The boundaries changed, so all item graphs are stale.
699             this._invalidateAllItems();
700         }
701
702         for (var requestId in this._staleRequests)
703             this._requestGridNode(this._staleRequests[requestId]).refreshGraph(this.calculator);
704
705         this._staleRequests = {};
706         this._sortItems();
707         this._updateSummaryBar();
708         this._dataGrid.updateWidths();
709         // FIXME: evaluate performance impact of moving this before a call to sortItems()
710         if (wasScrolledToLastRow)
711             this._dataGrid.scrollToLastRow();
712     },
713
714     _onRecordButtonClicked: function(e)
715     {
716         if (!this._recordButton.toggled)
717             this._reset();
718         this._recordButton.toggled = !this._recordButton.toggled;
719     },
720
721     _reset: function()
722     {
723         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ViewCleared);
724
725         this._clearSearchMatchedList();
726         if (this._popoverHelper)
727             this._popoverHelper.hidePopover();
728
729         if (this._calculator)
730             this._calculator.reset();
731
732         this._requests = [];
733         this._requestsById = {};
734         this._requestsByURL = {};
735         this._staleRequests = {};
736         this._requestGridNodes = {};
737
738         if (this._dataGrid) {
739             this._dataGrid.rootNode().removeChildren();
740             this._updateDividersIfNeeded();
741             this._updateSummaryBar();
742         }
743
744         this._mainRequestLoadTime = -1;
745         this._mainRequestDOMContentLoadedTime = -1;
746     },
747
748     get requests()
749     {
750         return this._requests;
751     },
752
753     /**
754      * @return {!WebInspector.NetworkRequest}
755      */
756     requestById: function(id)
757     {
758         return this._requestsById[id];
759     },
760
761     _onRequestStarted: function(event)
762     {
763         if (this._recordButton.toggled)
764             this._appendRequest(event.data);
765     },
766
767     _appendRequest: function(request)
768     {
769         this._requests.push(request);
770
771         // In case of redirect request id is reassigned to a redirected
772         // request and we need to update _requestsById and search results.
773         if (this._requestsById[request.requestId]) {
774             var oldRequest = request.redirects[request.redirects.length - 1];
775             this._requestsById[oldRequest.requestId] = oldRequest;
776
777             this._updateSearchMatchedListAfterRequestIdChanged(request.requestId, oldRequest.requestId);
778         }
779         this._requestsById[request.requestId] = request;
780
781         this._requestsByURL[request.url] = request;
782
783         // Pull all the redirects of the main request upon commit load.
784         if (request.redirects) {
785             for (var i = 0; i < request.redirects.length; ++i)
786                 this._refreshRequest(request.redirects[i]);
787         }
788
789         this._refreshRequest(request);
790     },
791
792     /**
793      * @param {!WebInspector.Event} event
794      */
795     _onRequestUpdated: function(event)
796     {
797         var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
798         this._refreshRequest(request);
799     },
800
801     /**
802      * @param {!WebInspector.NetworkRequest} request
803      */
804     _refreshRequest: function(request)
805     {
806         if (!this._requestsById[request.requestId])
807             return;
808         this._staleRequests[request.requestId] = request;
809         this._scheduleRefresh();
810     },
811
812     _willReloadPage: function(event)
813     {
814         this._recordButton.toggled = true;
815         if (!this._preserveLogCheckbox.checked())
816             this._reset();
817     },
818
819     /**
820      * @param {!WebInspector.Event} event
821      */
822     _mainFrameNavigated: function(event)
823     {
824         if (!this._recordButton.toggled || this._preserveLogCheckbox.checked())
825             return;
826
827         var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
828         var loaderId = frame.loaderId;
829
830         // Pick provisional load requests.
831         var requestsToPick = [];
832         var requests = WebInspector.networkLog.requests;
833         for (var i = 0; i < requests.length; ++i) {
834             var request = requests[i];
835             if (request.loaderId === loaderId)
836                 requestsToPick.push(request);
837         }
838
839         this._reset();
840
841         for (var i = 0; i < requestsToPick.length; ++i)
842             this._appendRequest(requestsToPick[i]);
843     },
844
845     switchToDetailedView: function()
846     {
847         if (!this._dataGrid)
848             return;
849         if (this._dataGrid.selectedNode)
850             this._dataGrid.selectedNode.selected = false;
851
852         this.element.classList.remove("brief-mode");
853         this._detailedMode = true;
854         this._updateColumns();
855     },
856
857     switchToBriefView: function()
858     {
859         this.element.classList.add("brief-mode");
860         this._removeAllNodeHighlights();
861         this._detailedMode = false;
862         this._updateColumns();
863         this._popoverHelper.hidePopover();
864     },
865
866     _toggleLargerRequests: function()
867     {
868         WebInspector.settings.resourcesLargeRows.set(!WebInspector.settings.resourcesLargeRows.get());
869         this._setLargerRequests(WebInspector.settings.resourcesLargeRows.get());
870     },
871
872     _setLargerRequests: function(enabled)
873     {
874         this._largerRequestsButton.toggled = enabled;
875         if (!enabled) {
876             this._largerRequestsButton.title = WebInspector.UIString("Use large resource rows.");
877             this._dataGrid.element.classList.add("small");
878             this._timelineGrid.element.classList.add("small");
879         } else {
880             this._largerRequestsButton.title = WebInspector.UIString("Use small resource rows.");
881             this._dataGrid.element.classList.remove("small");
882             this._timelineGrid.element.classList.remove("small");
883         }
884         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, { largeRows: enabled });
885         this._updateOffscreenRows();
886     },
887
888     _getPopoverAnchor: function(element)
889     {
890         if (!this._allowPopover)
891             return;
892         var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label");
893         if (anchor && anchor.parentElement.request && anchor.parentElement.request.timing)
894             return anchor;
895         anchor = element.enclosingNodeOrSelfWithClass("network-script-initiated");
896         if (anchor && anchor.request && anchor.request.initiator)
897             return anchor;
898
899         return null;
900     },
901
902     /**
903      * @param {!Element} anchor
904      * @param {!WebInspector.Popover} popover
905      */
906     _showPopover: function(anchor, popover)
907     {
908         var content;
909         if (anchor.classList.contains("network-script-initiated"))
910             content = this._generateScriptInitiatedPopoverContent(anchor.request);
911         else
912             content = WebInspector.RequestTimingView.createTimingTable(anchor.parentElement.request);
913         popover.show(content, anchor);
914     },
915
916     _onHidePopover: function()
917     {
918         this._linkifier.reset();
919     },
920
921     /**
922      * @param {!WebInspector.NetworkRequest} request
923      * @return {!Element}
924      */
925     _generateScriptInitiatedPopoverContent: function(request)
926     {
927         var stackTrace = request.initiator.stackTrace;
928         var framesTable = document.createElement("table");
929         for (var i = 0; i < stackTrace.length; ++i) {
930             var stackFrame = stackTrace[i];
931             var row = document.createElement("tr");
932             row.createChild("td").textContent = stackFrame.functionName || WebInspector.UIString("(anonymous function)");
933             row.createChild("td").textContent = " @ ";
934             row.createChild("td").appendChild(this._linkifier.linkifyLocation(stackFrame.url, stackFrame.lineNumber - 1, stackFrame.columnNumber - 1));
935             framesTable.appendChild(row);
936         }
937         return framesTable;
938     },
939
940     _updateColumns: function()
941     {
942         var columnsVisibility = this._coulmnsVisibilitySetting.get();
943         var detailedMode = !!this._detailedMode;
944         for (var columnIdentifier in columnsVisibility) {
945             var visible = detailedMode && columnsVisibility[columnIdentifier];
946             this._dataGrid.setColumnVisible(columnIdentifier, visible);
947         }
948         this._dataGrid.setColumnVisible("timeline", detailedMode);
949         this._dataGrid.applyColumnWeights();
950     },
951
952     /**
953      * @param {string} columnIdentifier
954      */
955     _toggleColumnVisibility: function(columnIdentifier)
956     {
957         var columnsVisibility = this._coulmnsVisibilitySetting.get();
958         columnsVisibility[columnIdentifier] = !columnsVisibility[columnIdentifier];
959         this._coulmnsVisibilitySetting.set(columnsVisibility);
960
961         this._updateColumns();
962     },
963
964     /**
965      * @return {!Array.<string>}
966      */
967     _getConfigurableColumnIDs: function()
968     {
969         if (this._configurableColumnIDs)
970             return this._configurableColumnIDs;
971
972         var columns = this._dataGrid.columns;
973         function compare(id1, id2)
974         {
975             return columns[id1].title.compareTo(columns[id2].title);
976         }
977
978         var columnIDs = Object.keys(this._coulmnsVisibilitySetting.get());
979         this._configurableColumnIDs = columnIDs.sort(compare);
980         return this._configurableColumnIDs;
981     },
982
983     _contextMenu: function(event)
984     {
985         var contextMenu = new WebInspector.ContextMenu(event);
986
987         if (this._detailedMode && event.target.isSelfOrDescendant(this._dataGrid.headerTableBody)) {
988             var columnsVisibility = this._coulmnsVisibilitySetting.get();
989             var columnIDs = this._getConfigurableColumnIDs();
990             for (var i = 0; i < columnIDs.length; ++i) {
991                 var columnIdentifier = columnIDs[i];
992                 var column = this._dataGrid.columns[columnIdentifier];
993                 contextMenu.appendCheckboxItem(column.title, this._toggleColumnVisibility.bind(this, columnIdentifier), !!columnsVisibility[columnIdentifier]);
994             }
995             contextMenu.show();
996             return;
997         }
998
999         var gridNode = this._dataGrid.dataGridNodeFromNode(event.target);
1000         var request = gridNode && gridNode._request;
1001
1002         if (request) {
1003             contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, request.url, false));
1004             contextMenu.appendSeparator();
1005             contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), this._copyLocation.bind(this, request));
1006             if (request.requestHeadersText())
1007                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy request headers" : "Copy Request Headers"), this._copyRequestHeaders.bind(this, request));
1008             if (request.responseHeadersText)
1009                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy response headers" : "Copy Response Headers"), this._copyResponseHeaders.bind(this, request));
1010             contextMenu.appendItem(WebInspector.UIString("Copy as cURL"), this._copyCurlCommand.bind(this, request));
1011         }
1012         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy all as HAR" : "Copy All as HAR"), this._copyAll.bind(this));
1013
1014         contextMenu.appendSeparator();
1015         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as HAR with content" : "Save as HAR with Content"), this._exportAll.bind(this));
1016
1017         contextMenu.appendSeparator();
1018         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cache" : "Clear Browser Cache"), this._clearBrowserCache.bind(this));
1019         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cookies" : "Clear Browser Cookies"), this._clearBrowserCookies.bind(this));
1020
1021         if (request && request.type === WebInspector.resourceTypes.XHR) {
1022             contextMenu.appendSeparator();
1023             contextMenu.appendItem(WebInspector.UIString("Replay XHR"), this._replayXHR.bind(this, request.requestId));
1024             contextMenu.appendSeparator();
1025         }
1026
1027         contextMenu.show();
1028     },
1029
1030     _replayXHR: function(requestId)
1031     {
1032         NetworkAgent.replayXHR(requestId);
1033     },
1034
1035     _copyAll: function()
1036     {
1037         var harArchive = {
1038             log: (new WebInspector.HARLog(this._requests.filter(WebInspector.NetworkLogView.HTTPRequestsFilter))).build()
1039         };
1040         InspectorFrontendHost.copyText(JSON.stringify(harArchive, null, 2));
1041     },
1042
1043     _copyLocation: function(request)
1044     {
1045         InspectorFrontendHost.copyText(request.url);
1046     },
1047
1048     _copyRequestHeaders: function(request)
1049     {
1050         InspectorFrontendHost.copyText(request.requestHeadersText());
1051     },
1052
1053     _copyResponseHeaders: function(request)
1054     {
1055         InspectorFrontendHost.copyText(request.responseHeadersText);
1056     },
1057
1058     /**
1059      * @param {!WebInspector.NetworkRequest} request
1060      */
1061     _copyCurlCommand: function(request)
1062     {
1063         InspectorFrontendHost.copyText(this._generateCurlCommand(request));
1064     },
1065
1066     _exportAll: function()
1067     {
1068         var filename = WebInspector.inspectedPageDomain + ".har";
1069         var stream = new WebInspector.FileOutputStream();
1070         stream.open(filename, openCallback.bind(this));
1071
1072         /**
1073          * @param {boolean} accepted
1074          * @this {WebInspector.NetworkLogView}
1075          */
1076         function openCallback(accepted)
1077         {
1078             if (!accepted)
1079                 return;
1080             var progressIndicator = new WebInspector.ProgressIndicator();
1081             this._progressBarContainer.appendChild(progressIndicator.element);
1082             var harWriter = new WebInspector.HARWriter();
1083             harWriter.write(stream, this._requests.filter(WebInspector.NetworkLogView.HTTPRequestsFilter), progressIndicator);
1084         }
1085     },
1086
1087     _clearBrowserCache: function()
1088     {
1089         if (confirm(WebInspector.UIString("Are you sure you want to clear browser cache?")))
1090             NetworkAgent.clearBrowserCache();
1091     },
1092
1093     _clearBrowserCookies: function()
1094     {
1095         if (confirm(WebInspector.UIString("Are you sure you want to clear browser cookies?")))
1096             NetworkAgent.clearBrowserCookies();
1097     },
1098
1099     _updateOffscreenRows: function()
1100     {
1101         var dataTableBody = this._dataGrid.dataTableBody;
1102         var rows = dataTableBody.children;
1103         var recordsCount = rows.length;
1104         if (recordsCount < 2)
1105             return;  // Filler row only.
1106
1107         var visibleTop = this._dataGrid.scrollContainer.scrollTop;
1108         var visibleBottom = visibleTop + this._dataGrid.scrollContainer.offsetHeight;
1109
1110         var rowHeight = 0;
1111
1112         // Filler is at recordsCount - 1.
1113         var unfilteredRowIndex = 0;
1114         for (var i = 0; i < recordsCount - 1; ++i) {
1115             var row = rows[i];
1116
1117             var dataGridNode = this._dataGrid.dataGridNodeFromNode(row);
1118             if (dataGridNode.isFilteredOut()) {
1119                 row.classList.remove("offscreen");
1120                 continue;
1121             }
1122
1123             if (!rowHeight)
1124                 rowHeight = row.offsetHeight;
1125
1126             var rowIsVisible = unfilteredRowIndex * rowHeight < visibleBottom && (unfilteredRowIndex + 1) * rowHeight > visibleTop;
1127             if (rowIsVisible !== row.rowIsVisible) {
1128                 row.enableStyleClass("offscreen", !rowIsVisible);
1129                 row.rowIsVisible = rowIsVisible;
1130             }
1131             var rowIsOdd = !!(unfilteredRowIndex & 1);
1132             if (rowIsOdd !== row.rowIsOdd) {
1133                 row.enableStyleClass("odd", rowIsOdd);
1134                 row.rowIsOdd = rowIsOdd;
1135             }
1136             unfilteredRowIndex++;
1137         }
1138     },
1139
1140     _matchRequest: function(request)
1141     {
1142         if (!this._searchRegExp)
1143             return -1;
1144
1145         if (!request.name().match(this._searchRegExp) && !request.path().match(this._searchRegExp))
1146             return -1;
1147
1148         if (request.requestId in this._matchedRequestsMap)
1149             return this._matchedRequestsMap[request.requestId];
1150
1151         var matchedRequestIndex = this._matchedRequests.length;
1152         this._matchedRequestsMap[request.requestId] = matchedRequestIndex;
1153         this._matchedRequests.push(request.requestId);
1154
1155         return matchedRequestIndex;
1156     },
1157
1158     _clearSearchMatchedList: function()
1159     {
1160         delete this._searchRegExp;
1161         this._matchedRequests = [];
1162         this._matchedRequestsMap = {};
1163         this._removeAllHighlights();
1164     },
1165
1166     _updateSearchMatchedListAfterRequestIdChanged: function(oldRequestId, newRequestId)
1167     {
1168         var requestIndex = this._matchedRequestsMap[oldRequestId];
1169         if (requestIndex) {
1170             delete this._matchedRequestsMap[oldRequestId];
1171             this._matchedRequestsMap[newRequestId] = requestIndex;
1172             this._matchedRequests[requestIndex] = newRequestId;
1173         }
1174     },
1175
1176     _updateHighlightIfMatched: function(request)
1177     {
1178         var matchedRequestIndex = this._matchRequest(request);
1179         if (matchedRequestIndex === -1)
1180             return;
1181
1182         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedRequests.length);
1183
1184         if (this._currentMatchedRequestIndex !== -1 && this._currentMatchedRequestIndex !== matchedRequestIndex)
1185             return;
1186
1187         this._highlightNthMatchedRequestForSearch(matchedRequestIndex, false);
1188     },
1189
1190     _removeAllHighlights: function()
1191     {
1192         this._removeAllNodeHighlights();
1193         for (var i = 0; i < this._highlightedSubstringChanges.length; ++i)
1194             WebInspector.revertDomChanges(this._highlightedSubstringChanges[i]);
1195         this._highlightedSubstringChanges = [];
1196     },
1197
1198     /**
1199      * @param {!WebInspector.NetworkRequest} request
1200      * @param {boolean} reveal
1201      * @param {!RegExp=} regExp
1202      */
1203     _highlightMatchedRequest: function(request, reveal, regExp)
1204     {
1205         var node = this._requestGridNode(request);
1206         if (!node)
1207             return;
1208
1209         var nameMatched = request.name().match(regExp);
1210         var pathMatched = request.path().match(regExp);
1211         if (!nameMatched && pathMatched && !this._largerRequestsButton.toggled)
1212             this._toggleLargerRequests();
1213         var highlightedSubstringChanges = node._highlightMatchedSubstring(regExp);
1214         this._highlightedSubstringChanges.push(highlightedSubstringChanges);
1215         if (reveal) {
1216             node.reveal();
1217             this._highlightNode(node);
1218         }
1219     },
1220
1221     /**
1222      * @param {number} matchedRequestIndex
1223      * @param {boolean} reveal
1224      */
1225     _highlightNthMatchedRequestForSearch: function(matchedRequestIndex, reveal)
1226     {
1227         var request = this.requestById(this._matchedRequests[matchedRequestIndex]);
1228         if (!request)
1229             return;
1230         this._removeAllHighlights();
1231         this._highlightMatchedRequest(request, reveal, this._searchRegExp);
1232         var node = this._requestGridNode(request);
1233         if (node)
1234             this._currentMatchedRequestIndex = matchedRequestIndex;
1235
1236         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._currentMatchedRequestIndex);
1237     },
1238
1239     /**
1240      * @param {string} query
1241      * @param {boolean} shouldJump
1242      */
1243     performSearch: function(query, shouldJump)
1244     {
1245         var newMatchedRequestIndex = 0;
1246         var currentMatchedRequestId;
1247         if (this._currentMatchedRequestIndex !== -1)
1248             currentMatchedRequestId = this._matchedRequests[this._currentMatchedRequestIndex];
1249
1250         this._clearSearchMatchedList();
1251         this._searchRegExp = createPlainTextSearchRegex(query, "i");
1252
1253         var childNodes = this._dataGrid.dataTableBody.childNodes;
1254         var requestNodes = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); // drop the filler row.
1255
1256         for (var i = 0; i < requestNodes.length; ++i) {
1257             var dataGridNode = this._dataGrid.dataGridNodeFromNode(requestNodes[i]);
1258             if (dataGridNode.isFilteredOut())
1259                 continue;
1260             if (this._matchRequest(dataGridNode._request) !== -1 && dataGridNode._request.requestId === currentMatchedRequestId)
1261                 newMatchedRequestIndex = this._matchedRequests.length - 1;
1262         }
1263
1264         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedRequests.length);
1265         if (shouldJump)
1266             this._highlightNthMatchedRequestForSearch(newMatchedRequestIndex, true);
1267     },
1268
1269     /**
1270      * @param {!WebInspector.NetworkDataGridNode} node
1271      */
1272     _applyFilter: function(node)
1273     {
1274         var filter = this._textFilterUI.regex();
1275         var request = node._request;
1276
1277         var matches = true;
1278         if (this._dataURLFilterUI.checked() && request.parsedURL.isDataURL())
1279             matches = false;
1280         if (matches && !this._resourceTypeFilterUI.accept(request.type.name()))
1281             matches = false;
1282
1283         if (matches && filter) {
1284             matches = filter.test(request.name()) || filter.test(request.path());
1285             if (matches)
1286                 this._highlightMatchedRequest(request, false, filter);
1287         }
1288
1289         node.element.enableStyleClass("filtered-out", !matches);
1290         if (matches)
1291             this._filteredOutRequests.remove(request);
1292         else
1293             this._filteredOutRequests.put(request, true);
1294     },
1295
1296     _filterRequests: function()
1297     {
1298         this._removeAllHighlights();
1299         this._filteredOutRequests.clear();
1300
1301         var nodes = this._dataGrid.rootNode().children;
1302         for (var i = 0; i < nodes.length; ++i)
1303             this._applyFilter(nodes[i]);
1304         this._updateSummaryBar();
1305         this._updateOffscreenRows();
1306     },
1307
1308     jumpToPreviousSearchResult: function()
1309     {
1310         if (!this._matchedRequests.length)
1311             return;
1312         this._highlightNthMatchedRequestForSearch((this._currentMatchedRequestIndex + this._matchedRequests.length - 1) % this._matchedRequests.length, true);
1313     },
1314
1315     jumpToNextSearchResult: function()
1316     {
1317         if (!this._matchedRequests.length)
1318             return;
1319         this._highlightNthMatchedRequestForSearch((this._currentMatchedRequestIndex + 1) % this._matchedRequests.length, true);
1320     },
1321
1322     searchCanceled: function()
1323     {
1324         this._clearSearchMatchedList();
1325         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, 0);
1326     },
1327
1328     revealAndHighlightRequest: function(request)
1329     {
1330         this._removeAllNodeHighlights();
1331
1332         var node = this._requestGridNode(request);
1333         if (node) {
1334             this._dataGrid.element.focus();
1335             node.reveal();
1336             this._highlightNode(node);
1337         }
1338     },
1339
1340     _removeAllNodeHighlights: function()
1341     {
1342         if (this._highlightedNode) {
1343             this._highlightedNode.element.classList.remove("highlighted-row");
1344             delete this._highlightedNode;
1345         }
1346     },
1347
1348     _highlightNode: function(node)
1349     {
1350         node.element.classList.add("highlighted-row");
1351         this._highlightedNode = node;
1352     },
1353
1354    /**
1355      * @param {!WebInspector.NetworkRequest} request
1356      * @return {string}
1357      */
1358     _generateCurlCommand: function(request)
1359     {
1360         var command = ["curl"];
1361         // These headers are derived from URL (except "version") and would be added by cURL anyway.
1362         var ignoredHeaders = {"host": 1, "method": 1, "path": 1, "scheme": 1, "version": 1};
1363
1364         function escapeStringWin(str)
1365         {
1366             /* Replace quote by double quote (but not by \") because it is
1367                recognized by both cmd.exe and MS Crt arguments parser.
1368
1369                Replace % by "%" because it could be expanded to an environment
1370                variable value. So %% becomes "%""%". Even if an env variable ""
1371                (2 doublequotes) is declared, the cmd.exe will not
1372                substitute it with its value.
1373
1374                Replace each backslash with double backslash to make sure
1375                MS Crt arguments parser won't collapse them.
1376
1377                Replace new line outside of quotes since cmd.exe doesn't let
1378                to do it inside.
1379             */
1380             return "\"" + str.replace(/"/g, "\"\"")
1381                              .replace(/%/g, "\"%\"")
1382                              .replace(/\\/g, "\\\\")
1383                              .replace(/[\r\n]+/g, "\"^$&\"") + "\"";
1384         }
1385
1386         function escapeStringPosix(str)
1387         {
1388             function escapeCharacter(x)
1389             {
1390                 var code = x.charCodeAt(0);
1391                 if (code < 256) {
1392                     // Add leading zero when needed to not care about the next character.
1393                     return code < 16 ? "\\x0" + code.toString(16) : "\\x" + code.toString(16);
1394                  }
1395                  code = code.toString(16);
1396                  return "\\u" + ("0000" + code).substr(code.length, 4);
1397              }
1398
1399             if (/[^\x20-\x7E]|\'/.test(str)) {
1400                 // Use ANSI-C quoting syntax.
1401                 return "$\'" + str.replace(/\\/g, "\\\\")
1402                                   .replace(/\'/g, "\\\'")
1403                                   .replace(/\n/g, "\\n")
1404                                   .replace(/\r/g, "\\r")
1405                                   .replace(/[^\x20-\x7E]/g, escapeCharacter) + "'";
1406             } else {
1407                 // Use single quote syntax.
1408                 return "'" + str + "'";
1409             }
1410         }
1411
1412         // cURL command expected to run on the same platform that DevTools run
1413         // (it may be different from the inspected page platform).
1414         var escapeString = WebInspector.isWin() ? escapeStringWin : escapeStringPosix;
1415
1416         command.push(escapeString(request.url).replace(/[[{}\]]/g, "\\$&"));
1417
1418         var inferredMethod = "GET";
1419         var data = [];
1420         var requestContentType = request.requestContentType();
1421         if (requestContentType && requestContentType.startsWith("application/x-www-form-urlencoded") && request.requestFormData) {
1422            data.push("--data");
1423            data.push(escapeString(request.requestFormData));
1424            ignoredHeaders["content-length"] = true;
1425            inferredMethod = "POST";
1426         } else if (request.requestFormData) {
1427            data.push("--data-binary");
1428            data.push(escapeString(request.requestFormData));
1429            ignoredHeaders["content-length"] = true;
1430            inferredMethod = "POST";
1431         }
1432
1433         if (request.requestMethod !== inferredMethod) {
1434             command.push("-X");
1435             command.push(request.requestMethod);
1436         }
1437
1438         var requestHeaders = request.requestHeaders();
1439         for (var i = 0; i < requestHeaders.length; i++) {
1440             var header = requestHeaders[i];
1441             var name = header.name.replace(/^:/, ""); // Translate SPDY v3 headers to HTTP headers.
1442             if (name.toLowerCase() in ignoredHeaders)
1443                 continue;
1444             command.push("-H");
1445             command.push(escapeString(name + ": " + header.value));
1446         }
1447         command = command.concat(data);
1448         command.push("--compressed");
1449         return command.join(" ");
1450     },
1451
1452     __proto__: WebInspector.View.prototype
1453 }
1454
1455 /**
1456  * @param {!WebInspector.NetworkRequest} request
1457  * @return {boolean}
1458  */
1459 WebInspector.NetworkLogView.HTTPRequestsFilter = function(request)
1460 {
1461     return request.parsedURL.isValid && (request.scheme in WebInspector.NetworkLogView.HTTPSchemas);
1462 }
1463
1464
1465 WebInspector.NetworkLogView.EventTypes = {
1466     ViewCleared: "ViewCleared",
1467     RowSizeChanged: "RowSizeChanged",
1468     RequestSelected: "RequestSelected",
1469     SearchCountUpdated: "SearchCountUpdated",
1470     SearchIndexUpdated: "SearchIndexUpdated"
1471 };
1472
1473 /**
1474  * @constructor
1475  * @implements {WebInspector.ContextMenu.Provider}
1476  * @implements {WebInspector.Searchable}
1477  * @extends {WebInspector.Panel}
1478  */
1479 WebInspector.NetworkPanel = function()
1480 {
1481     WebInspector.Panel.call(this, "network");
1482     this.registerRequiredCSS("networkPanel.css");
1483     this._injectStyles();
1484
1485     this._panelStatusBarElement = this.element.createChild("div", "panel-status-bar");
1486     this._filterBar = new WebInspector.FilterBar();
1487     this._filtersContainer = this.element.createChild("div", "network-filters-header hidden");
1488     this._filtersContainer.appendChild(this._filterBar.filtersElement());
1489     this._filterBar.addEventListener(WebInspector.FilterBar.Events.FiltersToggled, this._onFiltersToggled, this);
1490
1491     this._searchableView = new WebInspector.SearchableView(this);
1492     this._searchableView.show(this.element);
1493     this._contentsElement = this._searchableView.element;
1494
1495     this.createSidebarView(this._contentsElement);
1496     this.splitView.hideMainElement();
1497
1498     var defaultColumnsVisibility = WebInspector.NetworkLogView._defaultColumnsVisibility;
1499     var networkLogColumnsVisibilitySetting = WebInspector.settings.createSetting("networkLogColumnsVisibility", defaultColumnsVisibility);
1500     var savedColumnsVisibility = networkLogColumnsVisibilitySetting.get();
1501     var columnsVisibility = {};
1502     for (var columnId in defaultColumnsVisibility)
1503         columnsVisibility[columnId] = savedColumnsVisibility.hasOwnProperty(columnId) ? savedColumnsVisibility[columnId] : defaultColumnsVisibility[columnId];
1504     networkLogColumnsVisibilitySetting.set(columnsVisibility);
1505
1506     this._networkLogView = new WebInspector.NetworkLogView(this._filterBar, networkLogColumnsVisibilitySetting);
1507     this.splitView.setSidebarView(this._networkLogView);
1508
1509     this._viewsContainerElement = this.splitView.mainElement();
1510     this._viewsContainerElement.id = "network-views";
1511     this._viewsContainerElement.classList.add("hidden");
1512     if (!this._networkLogView.useLargeRows)
1513         this._viewsContainerElement.classList.add("small");
1514
1515     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ViewCleared, this._onViewCleared, this);
1516     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, this._onRowSizeChanged, this);
1517     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RequestSelected, this._onRequestSelected, this);
1518     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._onSearchCountUpdated, this);
1519     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._onSearchIndexUpdated, this);
1520
1521     this._closeButtonElement = this._viewsContainerElement.createChild("div", "close-button");
1522     this._closeButtonElement.id = "network-close-button";
1523     this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false);
1524     this._viewsContainerElement.appendChild(this._closeButtonElement);
1525
1526     for (var i = 0; i < this._networkLogView.statusBarItems.length; ++i)
1527         this._panelStatusBarElement.appendChild(this._networkLogView.statusBarItems[i]);
1528
1529     /**
1530      * @this {WebInspector.NetworkPanel}
1531      */
1532     function viewGetter()
1533     {
1534         return this.visibleView;
1535     }
1536     WebInspector.GoToLineDialog.install(this, viewGetter.bind(this));
1537 }
1538
1539 WebInspector.NetworkPanel.prototype = {
1540     _onFiltersToggled: function(event)
1541     {
1542         var toggled = /** @type {boolean} */ (event.data);
1543         this._filtersContainer.enableStyleClass("hidden", !toggled);
1544         this.element.enableStyleClass("filters-toggled", toggled);
1545     },
1546
1547     /**
1548      * @return {!Array.<!Element>}
1549      */
1550     elementsToRestoreScrollPositionsFor: function()
1551     {
1552         return this._networkLogView.elementsToRestoreScrollPositionsFor();
1553     },
1554
1555     /**
1556      * @return {!WebInspector.SearchableView}
1557      */
1558     searchableView: function()
1559     {
1560         return this._searchableView;
1561     },
1562
1563     // FIXME: only used by the layout tests, should not be exposed.
1564     _reset: function()
1565     {
1566         this._networkLogView._reset();
1567     },
1568
1569     handleShortcut: function(event)
1570     {
1571         if (this._viewingRequestMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
1572             this._toggleGridMode();
1573             event.handled = true;
1574             return;
1575         }
1576
1577         WebInspector.Panel.prototype.handleShortcut.call(this, event);
1578     },
1579
1580     wasShown: function()
1581     {
1582         WebInspector.Panel.prototype.wasShown.call(this);
1583     },
1584
1585     get requests()
1586     {
1587         return this._networkLogView.requests;
1588     },
1589
1590     /**
1591      * @return {!WebInspector.NetworkRequest}
1592      */
1593     requestById: function(id)
1594     {
1595         return this._networkLogView.requestById(id);
1596     },
1597
1598     _requestByAnchor: function(anchor)
1599     {
1600         return anchor.requestId ? this.requestById(anchor.requestId) : this._networkLogView._requestsByURL[anchor.href];
1601     },
1602
1603     /**
1604      * @param {!Element} anchor
1605      * @return {boolean}
1606      */
1607     showAnchorLocation: function(anchor)
1608     {
1609         var request = this._requestByAnchor(anchor);
1610         if (!request)
1611             return false;
1612         this.revealAndHighlightRequest(request)
1613         WebInspector.inspectorView.setCurrentPanel(this);
1614         return true;
1615     },
1616
1617     revealAndHighlightRequest: function(request)
1618     {
1619         this._toggleGridMode();
1620         if (request)
1621             this._networkLogView.revealAndHighlightRequest(request);
1622     },
1623
1624     _onViewCleared: function(event)
1625     {
1626         this._closeVisibleRequest();
1627         this._toggleGridMode();
1628         this._viewsContainerElement.removeChildren();
1629         this._viewsContainerElement.appendChild(this._closeButtonElement);
1630     },
1631
1632     _onRowSizeChanged: function(event)
1633     {
1634         this._viewsContainerElement.enableStyleClass("small", !event.data.largeRows);
1635     },
1636
1637     _onSearchCountUpdated: function(event)
1638     {
1639         this._searchableView.updateSearchMatchesCount(event.data);
1640     },
1641
1642     _onSearchIndexUpdated: function(event)
1643     {
1644         this._searchableView.updateCurrentMatchIndex(event.data);
1645     },
1646
1647     _onRequestSelected: function(event)
1648     {
1649         this._showRequest(event.data);
1650     },
1651
1652     _showRequest: function(request)
1653     {
1654         if (!request)
1655             return;
1656
1657         this._toggleViewingRequestMode();
1658
1659         if (this.visibleView) {
1660             this.visibleView.detach();
1661             delete this.visibleView;
1662         }
1663
1664         var view = new WebInspector.NetworkItemView(request);
1665         view.show(this._viewsContainerElement);
1666         this.visibleView = view;
1667     },
1668
1669     _closeVisibleRequest: function()
1670     {
1671         this.element.classList.remove("viewing-resource");
1672
1673         if (this.visibleView) {
1674             this.visibleView.detach();
1675             delete this.visibleView;
1676         }
1677     },
1678
1679     _toggleGridMode: function()
1680     {
1681         if (this._viewingRequestMode) {
1682             this._viewingRequestMode = false;
1683             this.element.classList.remove("viewing-resource");
1684             this.splitView.hideMainElement();
1685         }
1686
1687         this._networkLogView.switchToDetailedView();
1688         this._networkLogView.allowPopover = true;
1689         this._networkLogView._allowRequestSelection = false;
1690     },
1691
1692     _toggleViewingRequestMode: function()
1693     {
1694         if (this._viewingRequestMode)
1695             return;
1696         this._viewingRequestMode = true;
1697
1698         this.element.classList.add("viewing-resource");
1699         this.splitView.showMainElement();
1700         this._networkLogView.allowPopover = false;
1701         this._networkLogView._allowRequestSelection = true;
1702         this._networkLogView.switchToBriefView();
1703     },
1704
1705     /**
1706      * @param {string} query
1707      * @param {boolean} shouldJump
1708      */
1709     performSearch: function(query, shouldJump)
1710     {
1711         this._networkLogView.performSearch(query, shouldJump);
1712     },
1713
1714     jumpToPreviousSearchResult: function()
1715     {
1716         this._networkLogView.jumpToPreviousSearchResult();
1717     },
1718
1719     jumpToNextSearchResult: function()
1720     {
1721         this._networkLogView.jumpToNextSearchResult();
1722     },
1723
1724     searchCanceled: function()
1725     {
1726         this._networkLogView.searchCanceled();
1727     },
1728
1729     /**
1730      * @param {!WebInspector.ContextMenu} contextMenu
1731      * @param {!Object} target
1732      * @this {WebInspector.NetworkPanel}
1733      */
1734     appendApplicableItems: function(event, contextMenu, target)
1735     {
1736         /**
1737          * @this {WebInspector.NetworkPanel}
1738          */
1739         function reveal(request)
1740         {
1741             WebInspector.inspectorView.setCurrentPanel(this);
1742             this.revealAndHighlightRequest(request);
1743         }
1744
1745         /**
1746          * @this {WebInspector.NetworkPanel}
1747          */
1748         function appendRevealItem(request)
1749         {
1750             var revealText = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Network panel" : "Reveal in Network Panel");
1751             contextMenu.appendItem(revealText, reveal.bind(this, request));
1752         }
1753
1754         if (target instanceof WebInspector.Resource) {
1755             var resource = /** @type {!WebInspector.Resource} */ (target);
1756             if (resource.request)
1757                 appendRevealItem.call(this, resource.request);
1758             return;
1759         }
1760         if (target instanceof WebInspector.UISourceCode) {
1761             var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (target);
1762             var resource = WebInspector.resourceForURL(uiSourceCode.url);
1763             if (resource && resource.request)
1764                 appendRevealItem.call(this, resource.request);
1765             return;
1766         }
1767
1768         if (!(target instanceof WebInspector.NetworkRequest))
1769             return;
1770         var request = /** @type {!WebInspector.NetworkRequest} */ (target);
1771         if (this.visibleView && this.visibleView.isShowing() && this.visibleView.request() === request)
1772             return;
1773
1774         appendRevealItem.call(this, request);
1775     },
1776
1777     _injectStyles: function()
1778     {
1779         var style = document.createElement("style");
1780         var rules = [];
1781
1782         var columns = WebInspector.NetworkLogView._defaultColumnsVisibility;
1783
1784         var hideSelectors = [];
1785         var bgSelectors = [];
1786         for (var columnId in columns) {
1787             hideSelectors.push("#network-container .hide-" + columnId + "-column ." + columnId + "-column");
1788             bgSelectors.push(".network-log-grid.data-grid td." + columnId + "-column");
1789         }
1790         rules.push(hideSelectors.join(", ") + "{border-left: 0 none transparent;}");
1791         rules.push(bgSelectors.join(", ") + "{background-color: rgba(0, 0, 0, 0.07);}");
1792
1793         style.textContent = rules.join("\n");
1794         document.head.appendChild(style);
1795     },
1796
1797     __proto__: WebInspector.Panel.prototype
1798 }
1799
1800 /**
1801  * @constructor
1802  * @implements {WebInspector.ContextMenu.Provider}
1803  */
1804 WebInspector.NetworkPanel.ContextMenuProvider = function()
1805 {
1806 }
1807
1808 WebInspector.NetworkPanel.ContextMenuProvider.prototype = {
1809     /**
1810      * @param {!WebInspector.ContextMenu} contextMenu
1811      * @param {!Object} target
1812      */
1813     appendApplicableItems: function(event, contextMenu, target)
1814     {
1815         WebInspector.panel("network").appendApplicableItems(event, contextMenu, target);
1816     }
1817 }
1818
1819 /**
1820  * @constructor
1821  * @implements {WebInspector.TimelineGrid.Calculator}
1822  */
1823 WebInspector.NetworkBaseCalculator = function()
1824 {
1825 }
1826
1827 WebInspector.NetworkBaseCalculator.prototype = {
1828     /**
1829      * @param {number} time
1830      * @return {number}
1831      */
1832     computePosition: function(time)
1833     {
1834         return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea;
1835     },
1836
1837     /**
1838      * @return {!{start: number, middle: number, end: number}}
1839      */
1840     computeBarGraphPercentages: function(item)
1841     {
1842         return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan()) * 100};
1843     },
1844
1845     /**
1846      * @return {!{left: string, right: string, tooltip: string}}
1847      */
1848     computeBarGraphLabels: function(item)
1849     {
1850         const label = this.formatTime(this._value(item));
1851         return {left: label, right: label, tooltip: label};
1852     },
1853
1854     /**
1855      * @return {number}
1856      */
1857     boundarySpan: function()
1858     {
1859         return this._maximumBoundary - this._minimumBoundary;
1860     },
1861
1862     /**
1863      * @return {boolean}
1864      */
1865     updateBoundaries: function(item)
1866     {
1867         this._minimumBoundary = 0;
1868
1869         var value = this._value(item);
1870         if (typeof this._maximumBoundary === "undefined" || value > this._maximumBoundary) {
1871             this._maximumBoundary = value;
1872             return true;
1873         }
1874         return false;
1875     },
1876
1877     reset: function()
1878     {
1879         delete this._minimumBoundary;
1880         delete this._maximumBoundary;
1881     },
1882
1883     /**
1884      * @return {number}
1885      */
1886     maximumBoundary: function()
1887     {
1888         return this._maximumBoundary;
1889     },
1890
1891     /**
1892      * @return {number}
1893      */
1894     minimumBoundary: function()
1895     {
1896         return this._minimumBoundary;
1897     },
1898
1899     /**
1900      * @return {number}
1901      */
1902     zeroTime: function()
1903     {
1904         return this._minimumBoundary;
1905     },
1906
1907     /**
1908      * @return {number}
1909      */
1910     _value: function(item)
1911     {
1912         return 0;
1913     },
1914
1915     /**
1916      * @param {number} value
1917      * @param {boolean=} hires
1918      * @return {string}
1919      */
1920     formatTime: function(value, hires)
1921     {
1922         return value.toString();
1923     },
1924
1925     setDisplayWindow: function(clientWidth)
1926     {
1927         this._workingArea = clientWidth;
1928         this.paddingLeft = 0;
1929     }
1930 }
1931
1932 /**
1933  * @constructor
1934  * @extends {WebInspector.NetworkBaseCalculator}
1935  */
1936 WebInspector.NetworkTimeCalculator = function(startAtZero)
1937 {
1938     WebInspector.NetworkBaseCalculator.call(this);
1939     this.startAtZero = startAtZero;
1940 }
1941
1942 WebInspector.NetworkTimeCalculator.prototype = {
1943     /**
1944      * @param {!WebInspector.NetworkRequest} request
1945      * @return {!{start: number, middle: number, end: number}}
1946      */
1947     computeBarGraphPercentages: function(request)
1948     {
1949         if (request.startTime !== -1)
1950             var start = ((request.startTime - this._minimumBoundary) / this.boundarySpan()) * 100;
1951         else
1952             var start = 0;
1953
1954         if (request.responseReceivedTime !== -1)
1955             var middle = ((request.responseReceivedTime - this._minimumBoundary) / this.boundarySpan()) * 100;
1956         else
1957             var middle = (this.startAtZero ? start : 100);
1958
1959         if (request.endTime !== -1)
1960             var end = ((request.endTime - this._minimumBoundary) / this.boundarySpan()) * 100;
1961         else
1962             var end = (this.startAtZero ? middle : 100);
1963
1964         if (this.startAtZero) {
1965             end -= start;
1966             middle -= start;
1967             start = 0;
1968         }
1969
1970         return {start: start, middle: middle, end: end};
1971     },
1972
1973     /**
1974      * @return {number}
1975      */
1976     computePercentageFromEventTime: function(eventTime)
1977     {
1978         // This function computes a percentage in terms of the total loading time
1979         // of a specific event. If startAtZero is set, then this is useless, and we
1980         // want to return 0.
1981         if (eventTime !== -1 && !this.startAtZero)
1982             return ((eventTime - this._minimumBoundary) / this.boundarySpan()) * 100;
1983
1984         return 0;
1985     },
1986
1987     /**
1988      * @return {boolean}
1989      */
1990     updateBoundariesForEventTime: function(eventTime)
1991     {
1992         if (eventTime === -1 || this.startAtZero)
1993             return false;
1994
1995         if (typeof this._maximumBoundary === "undefined" || eventTime > this._maximumBoundary) {
1996             this._maximumBoundary = eventTime;
1997             return true;
1998         }
1999         return false;
2000     },
2001
2002     /**
2003      * @return {!{left: string, right: string, tooltip: (string|undefined)}}
2004      */
2005     computeBarGraphLabels: function(request)
2006     {
2007         var rightLabel = "";
2008         if (request.responseReceivedTime !== -1 && request.endTime !== -1)
2009             rightLabel = this.formatTime(request.endTime - request.responseReceivedTime);
2010
2011         var hasLatency = request.latency > 0;
2012         if (hasLatency)
2013             var leftLabel = this.formatTime(request.latency);
2014         else
2015             var leftLabel = rightLabel;
2016
2017         if (request.timing)
2018             return {left: leftLabel, right: rightLabel};
2019
2020         if (hasLatency && rightLabel) {
2021             var total = this.formatTime(request.duration);
2022             var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
2023         } else if (hasLatency)
2024             var tooltip = WebInspector.UIString("%s latency", leftLabel);
2025         else if (rightLabel)
2026             var tooltip = WebInspector.UIString("%s download", rightLabel);
2027
2028         if (request.cached)
2029             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
2030         return {left: leftLabel, right: rightLabel, tooltip: tooltip};
2031     },
2032
2033     /**
2034      * @return {boolean}
2035      */
2036     updateBoundaries: function(request)
2037     {
2038         var didChange = false;
2039
2040         var lowerBound;
2041         if (this.startAtZero)
2042             lowerBound = 0;
2043         else
2044             lowerBound = this._lowerBound(request);
2045
2046         if (lowerBound !== -1 && (typeof this._minimumBoundary === "undefined" || lowerBound < this._minimumBoundary)) {
2047             this._minimumBoundary = lowerBound;
2048             didChange = true;
2049         }
2050
2051         var upperBound = this._upperBound(request);
2052         if (upperBound !== -1 && (typeof this._maximumBoundary === "undefined" || upperBound > this._maximumBoundary)) {
2053             this._maximumBoundary = upperBound;
2054             didChange = true;
2055         }
2056
2057         return didChange;
2058     },
2059
2060     /**
2061      * @return {string}
2062      */
2063     formatTime: function(value)
2064     {
2065         return Number.secondsToString(value);
2066     },
2067
2068     _lowerBound: function(request)
2069     {
2070         return 0;
2071     },
2072
2073     _upperBound: function(request)
2074     {
2075         return 0;
2076     },
2077
2078     __proto__: WebInspector.NetworkBaseCalculator.prototype
2079 }
2080
2081 /**
2082  * @constructor
2083  * @extends {WebInspector.NetworkTimeCalculator}
2084  */
2085 WebInspector.NetworkTransferTimeCalculator = function()
2086 {
2087     WebInspector.NetworkTimeCalculator.call(this, false);
2088 }
2089
2090 WebInspector.NetworkTransferTimeCalculator.prototype = {
2091     /**
2092      * @param {number} value
2093      * @return {string}
2094      */
2095     formatTime: function(value)
2096     {
2097         return Number.secondsToString(value);
2098     },
2099
2100     _lowerBound: function(request)
2101     {
2102         return request.startTime;
2103     },
2104
2105     _upperBound: function(request)
2106     {
2107         return request.endTime;
2108     },
2109
2110     __proto__: WebInspector.NetworkTimeCalculator.prototype
2111 }
2112
2113 /**
2114  * @constructor
2115  * @extends {WebInspector.NetworkTimeCalculator}
2116  */
2117 WebInspector.NetworkTransferDurationCalculator = function()
2118 {
2119     WebInspector.NetworkTimeCalculator.call(this, true);
2120 }
2121
2122 WebInspector.NetworkTransferDurationCalculator.prototype = {
2123     /**
2124      * @param {number} value
2125      * @return {string}
2126      */
2127     formatTime: function(value)
2128     {
2129         return Number.secondsToString(value);
2130     },
2131
2132     _upperBound: function(request)
2133     {
2134         return request.duration;
2135     },
2136
2137     __proto__: WebInspector.NetworkTimeCalculator.prototype
2138 }
2139
2140 /**
2141  * @constructor
2142  * @extends {WebInspector.DataGridNode}
2143  * @param {!WebInspector.NetworkLogView} parentView
2144  * @param {!WebInspector.NetworkRequest} request
2145  */
2146 WebInspector.NetworkDataGridNode = function(parentView, request)
2147 {
2148     WebInspector.DataGridNode.call(this, {});
2149     this._parentView = parentView;
2150     this._request = request;
2151     this._linkifier = new WebInspector.Linkifier();
2152 }
2153
2154 WebInspector.NetworkDataGridNode.prototype = {
2155     /** override */
2156     createCells: function()
2157     {
2158         // Out of sight, out of mind: create nodes offscreen to save on render tree update times when running updateOffscreenRows()
2159         this._element.classList.add("offscreen");
2160         this._nameCell = this._createDivInTD("name");
2161         this._methodCell = this._createDivInTD("method");
2162         this._statusCell = this._createDivInTD("status");
2163         this._schemeCell = this._createDivInTD("scheme");
2164         this._domainCell = this._createDivInTD("domain");
2165         this._typeCell = this._createDivInTD("type");
2166         this._initiatorCell = this._createDivInTD("initiator");
2167         this._cookiesCell = this._createDivInTD("cookies");
2168         this._setCookiesCell = this._createDivInTD("setCookies");
2169         this._sizeCell = this._createDivInTD("size");
2170         this._timeCell = this._createDivInTD("time");
2171
2172         this._responseHeaderCells = {};
2173         var responseHeaderColumns = WebInspector.NetworkLogView._responseHeaderColumns;
2174         for (var i = 0; i < responseHeaderColumns.length; ++i)
2175             this._responseHeaderCells[responseHeaderColumns[i]] = this._createDivInTD(responseHeaderColumns[i]);
2176
2177         this._timelineCell = this._createDivInTD("timeline");
2178         this._createTimelineBar(this._timelineCell);
2179         this._nameCell.addEventListener("click", this._onClick.bind(this), false);
2180         this._nameCell.addEventListener("dblclick", this._openInNewTab.bind(this), false);
2181     },
2182
2183     wasDetached: function()
2184     {
2185         this._linkifier.reset();
2186     },
2187
2188     /**
2189      * @return {boolean}
2190      */
2191     isFilteredOut: function()
2192     {
2193         return !!this._parentView._filteredOutRequests.get(this._request);
2194     },
2195
2196     _onClick: function()
2197     {
2198         if (!this._parentView._allowRequestSelection)
2199             this.select();
2200     },
2201
2202     select: function()
2203     {
2204         this._parentView.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RequestSelected, this._request);
2205         WebInspector.DataGridNode.prototype.select.apply(this, arguments);
2206
2207         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
2208             action: WebInspector.UserMetrics.UserActionNames.NetworkRequestSelected,
2209             url: this._request.url
2210         });
2211     },
2212
2213     _highlightMatchedSubstring: function(regexp)
2214     {
2215         var domChanges = [];
2216         var matchInfo = this._element.textContent.match(regexp);
2217         if (matchInfo)
2218             WebInspector.highlightSearchResult(this._nameCell, matchInfo.index, matchInfo[0].length, domChanges);
2219         return domChanges;
2220     },
2221
2222     _openInNewTab: function()
2223     {
2224         InspectorFrontendHost.openInNewTab(this._request.url);
2225     },
2226
2227     get selectable()
2228     {
2229         return this._parentView._allowRequestSelection && !this.isFilteredOut();
2230     },
2231
2232     _createDivInTD: function(columnIdentifier)
2233     {
2234         var td = this.createTD(columnIdentifier);
2235         var div = td.createChild("div");
2236         this._element.appendChild(td);
2237         return div;
2238     },
2239
2240     /**
2241      * @param {!Element} cell
2242      */
2243     _createTimelineBar: function(cell)
2244     {
2245         cell.className = "network-graph-side";
2246
2247         this._barAreaElement = document.createElement("div");
2248         //    this._barAreaElement.className = "network-graph-bar-area hidden";
2249         this._barAreaElement.className = "network-graph-bar-area";
2250         this._barAreaElement.request = this._request;
2251         cell.appendChild(this._barAreaElement);
2252
2253         this._barLeftElement = document.createElement("div");
2254         this._barLeftElement.className = "network-graph-bar waiting";
2255         this._barAreaElement.appendChild(this._barLeftElement);
2256
2257         this._barRightElement = document.createElement("div");
2258         this._barRightElement.className = "network-graph-bar";
2259         this._barAreaElement.appendChild(this._barRightElement);
2260
2261
2262         this._labelLeftElement = document.createElement("div");
2263         this._labelLeftElement.className = "network-graph-label waiting";
2264         this._barAreaElement.appendChild(this._labelLeftElement);
2265
2266         this._labelRightElement = document.createElement("div");
2267         this._labelRightElement.className = "network-graph-label";
2268         this._barAreaElement.appendChild(this._labelRightElement);
2269
2270         cell.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false);
2271     },
2272
2273     refreshRequest: function()
2274     {
2275         this._refreshNameCell();
2276         this._refreshMethodCell();
2277         this._refreshStatusCell();
2278         this._refreshSchemeCell();
2279         this._refreshDomainCell();
2280         this._refreshTypeCell();
2281         this._refreshInitiatorCell();
2282         this._refreshCookiesCell();
2283         this._refreshSetCookiesCell();
2284         this._refreshSizeCell();
2285         this._refreshTimeCell();
2286
2287         var responseHeaderColumns = WebInspector.NetworkLogView._responseHeaderColumns;
2288         for (var i = 0; i < responseHeaderColumns.length; ++i)
2289             this._refreshResponseHeaderCell(responseHeaderColumns[i]);
2290
2291         if (this._request.cached)
2292             this._timelineCell.classList.add("resource-cached");
2293
2294         this._element.classList.add("network-item");
2295         this._element.enableStyleClass("network-error-row", this._request.failed || (this._request.statusCode >= 400));
2296         this._updateElementStyleClasses(this._element);
2297     },
2298
2299     /**
2300      * @param {!Element} element
2301      */
2302     _updateElementStyleClasses: function(element)
2303     {
2304         var typeClassName = "network-type-" + this._request.type.name();
2305         if (!element.classList.contains(typeClassName)) {
2306             element.removeMatchingStyleClasses("network-type-\\w+");
2307             element.classList.add(typeClassName);
2308         }
2309     },
2310
2311     _refreshResponseHeaderCell: function(headerName)
2312     {
2313         var cell = this._responseHeaderCells[headerName];
2314         var value = this._request.responseHeaderValue(headerName);
2315         cell.setTextAndTitle(value ? value : "");
2316     },
2317
2318     _refreshNameCell: function()
2319     {
2320         this._nameCell.removeChildren();
2321
2322         if (this._request.type === WebInspector.resourceTypes.Image) {
2323             var previewImage = document.createElement("img");
2324             previewImage.className = "image-network-icon-preview";
2325             this._request.populateImageSource(previewImage);
2326
2327             var iconElement = document.createElement("div");
2328             iconElement.className = "icon";
2329             iconElement.appendChild(previewImage);
2330         } else {
2331             var iconElement = document.createElement("img");
2332             iconElement.className = "icon";
2333         }
2334         this._nameCell.appendChild(iconElement);
2335         this._nameCell.appendChild(document.createTextNode(this._request.name()));
2336         this._appendSubtitle(this._nameCell, this._request.path());
2337         this._nameCell.title = this._request.url;
2338     },
2339
2340     _refreshMethodCell: function()
2341     {
2342         this._methodCell.setTextAndTitle(this._request.requestMethod);
2343     },
2344
2345     _refreshStatusCell: function()
2346     {
2347         this._statusCell.removeChildren();
2348
2349         if (this._request.failed) {
2350             var failText = this._request.canceled ? WebInspector.UIString("(canceled)") : WebInspector.UIString("(failed)");
2351             if (this._request.localizedFailDescription) {
2352                 this._statusCell.appendChild(document.createTextNode(failText));
2353                 this._appendSubtitle(this._statusCell, this._request.localizedFailDescription);
2354                 this._statusCell.title = failText + " " + this._request.localizedFailDescription;
2355             } else
2356                 this._statusCell.setTextAndTitle(failText);
2357             this._statusCell.classList.add("network-dim-cell");
2358             return;
2359         }
2360
2361         this._statusCell.classList.remove("network-dim-cell");
2362
2363         if (this._request.statusCode) {
2364             this._statusCell.appendChild(document.createTextNode("" + this._request.statusCode));
2365             this._appendSubtitle(this._statusCell, this._request.statusText);
2366             this._statusCell.title = this._request.statusCode + " " + this._request.statusText;
2367             if (this._request.cached)
2368                 this._statusCell.classList.add("network-dim-cell");
2369         } else {
2370             if (this._request.parsedURL.isDataURL())
2371                 this._statusCell.setTextAndTitle(WebInspector.UIString("(data)"));
2372             else if (this._request.isPingRequest())
2373                 this._statusCell.setTextAndTitle(WebInspector.UIString("(ping)"));
2374             else if (this._request.finished)
2375                 this._statusCell.setTextAndTitle(WebInspector.UIString("Finished"));
2376             else
2377                 this._statusCell.setTextAndTitle(WebInspector.UIString("(pending)"));
2378             this._statusCell.classList.add("network-dim-cell");
2379         }
2380     },
2381
2382     _refreshSchemeCell: function()
2383     {
2384         this._schemeCell.setTextAndTitle(this._request.scheme);
2385     },
2386
2387     _refreshDomainCell: function()
2388     {
2389         this._domainCell.setTextAndTitle(this._request.domain);
2390     },
2391
2392     _refreshTypeCell: function()
2393     {
2394         if (this._request.mimeType) {
2395             this._typeCell.classList.remove("network-dim-cell");
2396             this._typeCell.setTextAndTitle(this._request.mimeType);
2397         } else {
2398             this._typeCell.enableStyleClass("network-dim-cell", !this._request.isPingRequest());
2399             this._typeCell.setTextAndTitle(this._request.requestContentType() || "");
2400         }
2401     },
2402
2403     _refreshInitiatorCell: function()
2404     {
2405         this._initiatorCell.removeChildren();
2406         this._initiatorCell.classList.remove("network-dim-cell");
2407         this._initiatorCell.classList.remove("network-script-initiated");
2408         delete this._initiatorCell.request;
2409
2410         var request = this._request;
2411         var initiator = request.initiatorInfo();
2412
2413         switch (initiator.type) {
2414         case WebInspector.NetworkRequest.InitiatorType.Parser:
2415             this._initiatorCell.title = initiator.url + ":" + initiator.lineNumber;
2416             this._initiatorCell.appendChild(WebInspector.linkifyResourceAsNode(initiator.url, initiator.lineNumber - 1));
2417             this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Parser"));
2418             break;
2419
2420         case WebInspector.NetworkRequest.InitiatorType.Redirect:
2421             this._initiatorCell.title = initiator.url;
2422             console.assert(request.redirectSource);
2423             var redirectSource = /** @type {!WebInspector.NetworkRequest} */ (request.redirectSource);
2424             this._initiatorCell.appendChild(WebInspector.linkifyRequestAsNode(redirectSource));
2425             this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Redirect"));
2426             break;
2427
2428         case WebInspector.NetworkRequest.InitiatorType.Script:
2429             var urlElement = this._linkifier.linkifyLocation(initiator.url, initiator.lineNumber - 1, initiator.columnNumber - 1);
2430             urlElement.title = "";
2431             this._initiatorCell.appendChild(urlElement);
2432             this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Script"));
2433             this._initiatorCell.classList.add("network-script-initiated");
2434             this._initiatorCell.request = request;
2435             break;
2436
2437         default:
2438             this._initiatorCell.title = "";
2439             this._initiatorCell.classList.add("network-dim-cell");
2440             this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other"));
2441         }
2442     },
2443
2444     _refreshCookiesCell: function()
2445     {
2446         var requestCookies = this._request.requestCookies;
2447         this._cookiesCell.setTextAndTitle(requestCookies ? "" + requestCookies.length : "");
2448     },
2449
2450     _refreshSetCookiesCell: function()
2451     {
2452         var responseCookies = this._request.responseCookies;
2453         this._setCookiesCell.setTextAndTitle(responseCookies ? "" + responseCookies.length : "");
2454     },
2455
2456     _refreshSizeCell: function()
2457     {
2458         if (this._request.cached) {
2459             this._sizeCell.setTextAndTitle(WebInspector.UIString("(from cache)"));
2460             this._sizeCell.classList.add("network-dim-cell");
2461         } else {
2462             var resourceSize = Number.bytesToString(this._request.resourceSize);
2463             var transferSize = Number.bytesToString(this._request.transferSize);
2464             this._sizeCell.setTextAndTitle(transferSize);
2465             this._sizeCell.classList.remove("network-dim-cell");
2466             this._appendSubtitle(this._sizeCell, resourceSize);
2467         }
2468     },
2469
2470     _refreshTimeCell: function()
2471     {
2472         if (this._request.duration > 0) {
2473             this._timeCell.classList.remove("network-dim-cell");
2474             this._timeCell.setTextAndTitle(Number.secondsToString(this._request.duration));
2475             this._appendSubtitle(this._timeCell, Number.secondsToString(this._request.latency));
2476         } else {
2477             this._timeCell.classList.add("network-dim-cell");
2478             this._timeCell.setTextAndTitle(WebInspector.UIString("Pending"));
2479         }
2480     },
2481
2482     _appendSubtitle: function(cellElement, subtitleText)
2483     {
2484         var subtitleElement = document.createElement("div");
2485         subtitleElement.className = "network-cell-subtitle";
2486         subtitleElement.textContent = subtitleText;
2487         cellElement.appendChild(subtitleElement);
2488     },
2489
2490     refreshGraph: function(calculator)
2491     {
2492         var percentages = calculator.computeBarGraphPercentages(this._request);
2493         this._percentages = percentages;
2494
2495         this._barAreaElement.classList.remove("hidden");
2496         this._updateElementStyleClasses(this._timelineCell);
2497
2498         this._barLeftElement.style.setProperty("left", percentages.start + "%");
2499         this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
2500
2501         this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
2502         this._barRightElement.style.setProperty("left", percentages.middle + "%");
2503
2504         var labels = calculator.computeBarGraphLabels(this._request);
2505         this._labelLeftElement.textContent = labels.left;
2506         this._labelRightElement.textContent = labels.right;
2507
2508         var tooltip = (labels.tooltip || "");
2509         this._barLeftElement.title = tooltip;
2510         this._labelLeftElement.title = tooltip;
2511         this._labelRightElement.title = tooltip;
2512         this._barRightElement.title = tooltip;
2513     },
2514
2515     _refreshLabelPositions: function()
2516     {
2517         if (!this._percentages)
2518             return;
2519         this._labelLeftElement.style.removeProperty("left");
2520         this._labelLeftElement.style.removeProperty("right");
2521         this._labelLeftElement.classList.remove("before");
2522         this._labelLeftElement.classList.remove("hidden");
2523
2524         this._labelRightElement.style.removeProperty("left");
2525         this._labelRightElement.style.removeProperty("right");
2526         this._labelRightElement.classList.remove("after");
2527         this._labelRightElement.classList.remove("hidden");
2528
2529         const labelPadding = 10;
2530         const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
2531         const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;
2532
2533         if (this._barLeftElement) {
2534             var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
2535             var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
2536         } else {
2537             var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
2538             var rightBarWidth = barRightElementOffsetWidth - labelPadding;
2539         }
2540
2541         const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
2542         const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;
2543
2544         const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
2545         const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
2546         const graphElementOffsetWidth = this._timelineCell.offsetWidth;
2547
2548         if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
2549             var leftHidden = true;
2550
2551         if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
2552             var rightHidden = true;
2553
2554         if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
2555             // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
2556             if (labelBefore && !labelAfter)
2557                 leftHidden = true;
2558             else if (labelAfter && !labelBefore)
2559                 rightHidden = true;
2560         }
2561
2562         if (labelBefore) {
2563             if (leftHidden)
2564                 this._labelLeftElement.classList.add("hidden");
2565             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
2566             this._labelLeftElement.classList.add("before");
2567         } else {
2568             this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
2569             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
2570         }
2571
2572         if (labelAfter) {
2573             if (rightHidden)
2574                 this._labelRightElement.classList.add("hidden");
2575             this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
2576             this._labelRightElement.classList.add("after");
2577         } else {
2578             this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
2579             this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
2580         }
2581     },
2582
2583     __proto__: WebInspector.DataGridNode.prototype
2584 }
2585
2586 WebInspector.NetworkDataGridNode.NameComparator = function(a, b)
2587 {
2588     var aFileName = a._request.name();
2589     var bFileName = b._request.name();
2590     if (aFileName > bFileName)
2591         return 1;
2592     if (bFileName > aFileName)
2593         return -1;
2594     return 0;
2595 }
2596
2597 WebInspector.NetworkDataGridNode.SizeComparator = function(a, b)
2598 {
2599     if (b._request.cached && !a._request.cached)
2600         return 1;
2601     if (a._request.cached && !b._request.cached)
2602         return -1;
2603
2604     return a._request.transferSize - b._request.transferSize;
2605 }
2606
2607 WebInspector.NetworkDataGridNode.InitiatorComparator = function(a, b)
2608 {
2609     var aInitiator = a._request.initiatorInfo();
2610     var bInitiator = b._request.initiatorInfo();
2611
2612     if (aInitiator.type < bInitiator.type)
2613         return -1;
2614     if (aInitiator.type > bInitiator.type)
2615         return 1;
2616
2617     if (aInitiator.source < bInitiator.source)
2618         return -1;
2619     if (aInitiator.source > bInitiator.source)
2620         return 1;
2621
2622     if (aInitiator.lineNumber < bInitiator.lineNumber)
2623         return -1;
2624     if (aInitiator.lineNumber > bInitiator.lineNumber)
2625         return 1;
2626
2627     if (aInitiator.columnNumber < bInitiator.columnNumber)
2628         return -1;
2629     if (aInitiator.columnNumber > bInitiator.columnNumber)
2630         return 1;
2631
2632     return 0;
2633 }
2634
2635 WebInspector.NetworkDataGridNode.RequestCookiesCountComparator = function(a, b)
2636 {
2637     var aScore = a._request.requestCookies ? a._request.requestCookies.length : 0;
2638     var bScore = b._request.requestCookies ? b._request.requestCookies.length : 0;
2639     return aScore - bScore;
2640 }
2641
2642 WebInspector.NetworkDataGridNode.ResponseCookiesCountComparator = function(a, b)
2643 {
2644     var aScore = a._request.responseCookies ? a._request.responseCookies.length : 0;
2645     var bScore = b._request.responseCookies ? b._request.responseCookies.length : 0;
2646     return aScore - bScore;
2647 }
2648
2649 WebInspector.NetworkDataGridNode.RequestPropertyComparator = function(propertyName, revert, a, b)
2650 {
2651     var aValue = a._request[propertyName];
2652     var bValue = b._request[propertyName];
2653     if (aValue > bValue)
2654         return revert ? -1 : 1;
2655     if (bValue > aValue)
2656         return revert ? 1 : -1;
2657     return 0;
2658 }