Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / network / 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 /**
32  * @constructor
33  * @implements {WebInspector.Searchable}
34  * @implements {WebInspector.TargetManager.Observer}
35  * @extends {WebInspector.VBox}
36  * @param {!WebInspector.FilterBar} filterBar
37  * @param {!WebInspector.Setting} coulmnsVisibilitySetting
38  */
39 WebInspector.NetworkLogView = function(filterBar, coulmnsVisibilitySetting)
40 {
41     WebInspector.VBox.call(this);
42     this.registerRequiredCSS("network/networkLogView.css");
43     this.registerRequiredCSS("ui/filter.css");
44
45     this._filterBar = filterBar;
46     this._coulmnsVisibilitySetting = coulmnsVisibilitySetting;
47     this._allowRequestSelection = false;
48     /** @type {!Map.<string, !WebInspector.NetworkDataGridNode>} */
49     this._nodesByRequestId = new Map();
50     /** @type {!Object.<string, boolean>} */
51     this._staleRequestIds = {};
52     /** @type {number} */
53     this._mainRequestLoadTime = -1;
54     /** @type {number} */
55     this._mainRequestDOMContentLoadedTime = -1;
56     this._matchedRequestCount = 0;
57     this._highlightedSubstringChanges = [];
58
59     /** @type {!Array.<!WebInspector.NetworkLogView.Filter>} */
60     this._filters = [];
61
62     this._currentMatchedRequestNode = null;
63     this._currentMatchedRequestIndex = -1;
64
65     this._createStatusbarButtons();
66     this._createStatusBarItems();
67     this._linkifier = new WebInspector.Linkifier();
68
69     this._allowPopover = true;
70
71     /** @type {number} */
72     this._rowHeight = 0;
73
74     this._addFilters();
75     this._resetSuggestionBuilder();
76     this._initializeView();
77     this._toggleRecordButton(true);
78
79     WebInspector.targetManager.observeTargets(this);
80     WebInspector.targetManager.addModelListener(WebInspector.NetworkManager, WebInspector.NetworkManager.EventTypes.RequestStarted, this._onRequestStarted, this);
81     WebInspector.targetManager.addModelListener(WebInspector.NetworkManager, WebInspector.NetworkManager.EventTypes.RequestUpdated, this._onRequestUpdated, this);
82     WebInspector.targetManager.addModelListener(WebInspector.NetworkManager, WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestUpdated, this);
83
84     WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.WillReloadPage, this._willReloadPage, this);
85     WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this);
86     WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.Load, this._loadEventFired, this);
87     WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, this._domContentLoadedEventFired, this);
88 }
89
90 WebInspector.NetworkLogView.HTTPSchemas = {"http": true, "https": true, "ws": true, "wss": true};
91 WebInspector.NetworkLogView._responseHeaderColumns = ["Cache-Control", "Connection", "Content-Encoding", "Content-Length", "ETag", "Keep-Alive", "Last-Modified", "Server", "Vary"];
92 WebInspector.NetworkLogView.defaultColumnsVisibility = {
93     method: true, status: true, scheme: false, domain: false, remoteAddress: false, type: true, initiator: true, cookies: false, setCookies: false, size: true, time: true, connectionId: false,
94     "Cache-Control": false, "Connection": false, "Content-Encoding": false, "Content-Length": false, "ETag": false, "Keep-Alive": false, "Last-Modified": false, "Server": false, "Vary": false
95 };
96 WebInspector.NetworkLogView._defaultRefreshDelay = 500;
97
98 /** @enum {string} */
99 WebInspector.NetworkLogView.FilterType = {
100     Domain: "Domain",
101     HasResponseHeader: "HasResponseHeader",
102     Is: "Is",
103     Method: "Method",
104     MimeType: "MimeType",
105     Scheme: "Scheme",
106     SetCookieDomain: "SetCookieDomain",
107     SetCookieName: "SetCookieName",
108     SetCookieValue: "SetCookieValue",
109     StatusCode: "StatusCode"
110 };
111
112 /** @enum {string} */
113 WebInspector.NetworkLogView.IsFilterType = {
114     Running: "running"
115 };
116
117 /** @type {!Array.<string>} */
118 WebInspector.NetworkLogView._searchKeys = Object.values(WebInspector.NetworkLogView.FilterType);
119
120 /** @type {!Object.<string, string>} */
121 WebInspector.NetworkLogView._columnTitles = {
122     "name": WebInspector.UIString("Name"),
123     "method": WebInspector.UIString("Method"),
124     "status": WebInspector.UIString("Status"),
125     "scheme": WebInspector.UIString("Scheme"),
126     "domain": WebInspector.UIString("Domain"),
127     "remoteAddress": WebInspector.UIString("Remote Address"),
128     "type": WebInspector.UIString("Type"),
129     "initiator": WebInspector.UIString("Initiator"),
130     "cookies": WebInspector.UIString("Cookies"),
131     "setCookies": WebInspector.UIString("Set-Cookies"),
132     "size": WebInspector.UIString("Size"),
133     "time": WebInspector.UIString("Time"),
134     "connectionId": WebInspector.UIString("Connection Id"),
135     "timeline": WebInspector.UIString("Timeline"),
136
137     // Response header columns
138     "Cache-Control": WebInspector.UIString("Cache-Control"),
139     "Connection": WebInspector.UIString("Connection"),
140     "Content-Encoding": WebInspector.UIString("Content-Encoding"),
141     "Content-Length": WebInspector.UIString("Content-Length"),
142     "ETag": WebInspector.UIString("ETag"),
143     "Keep-Alive": WebInspector.UIString("Keep-Alive"),
144     "Last-Modified": WebInspector.UIString("Last-Modified"),
145     "Server": WebInspector.UIString("Server"),
146     "Vary": WebInspector.UIString("Vary")
147 };
148
149 WebInspector.NetworkLogView.prototype = {
150     /**
151      * @param {!WebInspector.Target} target
152      */
153     targetAdded: function(target)
154     {
155         target.networkLog.requests.forEach(this._appendRequest.bind(this));
156     },
157
158     /**
159      * @param {!WebInspector.Target} target
160      */
161     targetRemoved: function(target)
162     {
163     },
164
165     /**
166      * @return {boolean}
167      */
168     allowRequestSelection: function()
169     {
170         return this._allowRequestSelection;
171     },
172
173     _addFilters: function()
174     {
175         this._textFilterUI = new WebInspector.TextFilterUI();
176         this._textFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._filterChanged, this);
177         this._filterBar.addFilter(this._textFilterUI);
178
179         var types = [];
180         for (var typeId in WebInspector.resourceTypes) {
181             var resourceType = WebInspector.resourceTypes[typeId];
182             if (resourceType === WebInspector.resourceTypes.TextTrack)
183                 continue;
184             types.push({name: resourceType.name(), label: resourceType.categoryTitle()});
185         }
186         this._resourceTypeFilterUI = new WebInspector.NamedBitSetFilterUI(types, WebInspector.settings.networkResourceTypeFilters);
187         this._resourceTypeFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
188         this._filterBar.addFilter(this._resourceTypeFilterUI);
189
190         var dataURLSetting = WebInspector.settings.networkHideDataURL;
191         this._dataURLFilterUI = new WebInspector.CheckboxFilterUI("hide-data-url", WebInspector.UIString("Hide data URLs"), true, dataURLSetting);
192         this._dataURLFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
193         this._filterBar.addFilter(this._dataURLFilterUI);
194     },
195
196     _resetSuggestionBuilder: function()
197     {
198         this._suggestionBuilder = new WebInspector.FilterSuggestionBuilder(WebInspector.NetworkLogView._searchKeys);
199         this._suggestionBuilder.addItem(WebInspector.NetworkLogView.FilterType.Is, WebInspector.NetworkLogView.IsFilterType.Running);
200         this._textFilterUI.setSuggestionBuilder(this._suggestionBuilder);
201     },
202
203     /**
204      * @param {!WebInspector.Event} event
205      */
206     _filterChanged: function(event)
207     {
208         this._removeAllNodeHighlights();
209         this._parseFilterQuery(this._textFilterUI.value());
210         this._filterRequests();
211     },
212
213     _initializeView: function()
214     {
215         this.element.id = "network-container";
216
217         this._createSortingFunctions();
218         this._createCalculators();
219         this._createTable();
220         this._createTimelineGrid();
221         this._summaryBarElement = this.element.createChild("div", "network-summary-bar");
222
223         this._updateRowsSize();
224
225         this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), this._onHidePopover.bind(this));
226         // Enable faster hint.
227         this._popoverHelper.setTimeout(250, 250);
228
229         this.switchViewMode(true);
230     },
231
232     /**
233      * @return {!Array.<!Element>}
234      */
235     statusBarItems: function()
236     {
237         return [
238             this._recordButton.element,
239             this._clearButton.element,
240             this._filterBar.filterButton().element,
241             this._largerRequestsButton.element,
242             this._preserveLogCheckbox.element,
243             this._disableCacheCheckbox.element,
244             this._progressBarContainer];
245     },
246
247     /**
248      * @return {boolean}
249      */
250     usesLargeRows: function()
251     {
252         return !!WebInspector.settings.resourcesLargeRows.get();
253     },
254
255     /**
256      * @param {boolean} flag
257      */
258     setAllowPopover: function(flag)
259     {
260         this._allowPopover = flag;
261     },
262
263     /**
264      * @return {!Array.<!Element>}
265      */
266     elementsToRestoreScrollPositionsFor: function()
267     {
268         if (!this._dataGrid) // Not initialized yet.
269             return [];
270         return [this._dataGrid.scrollContainer];
271     },
272
273     _createTimelineGrid: function()
274     {
275         this._timelineGrid = new WebInspector.TimelineGrid();
276         this._timelineGrid.element.classList.add("network-timeline-grid");
277         this._dataGrid.element.appendChild(this._timelineGrid.element);
278     },
279
280     _createTable: function()
281     {
282         var columns = [];
283         columns.push({
284             id: "name",
285             titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path")),
286             title: WebInspector.NetworkLogView._columnTitles["name"],
287             sortable: true,
288             weight: 20,
289             disclosure: true
290         });
291
292         columns.push({
293             id: "method",
294             title: WebInspector.NetworkLogView._columnTitles["method"],
295             sortable: true,
296             weight: 6
297         });
298
299         columns.push({
300             id: "status",
301             titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text")),
302             title: WebInspector.NetworkLogView._columnTitles["status"],
303             sortable: true,
304             weight: 6
305         });
306
307         columns.push({
308             id: "scheme",
309             title: WebInspector.NetworkLogView._columnTitles["scheme"],
310             sortable: true,
311             weight: 6
312         });
313
314         columns.push({
315             id: "domain",
316             title: WebInspector.NetworkLogView._columnTitles["domain"],
317             sortable: true,
318             weight: 6
319         });
320
321         columns.push({
322             id: "remoteAddress",
323             title: WebInspector.NetworkLogView._columnTitles["remoteAddress"],
324             sortable: true,
325             weight: 10,
326             align: WebInspector.DataGrid.Align.Right
327         });
328
329         columns.push({
330             id: "type",
331             title: WebInspector.NetworkLogView._columnTitles["type"],
332             sortable: true,
333             weight: 6
334         });
335
336         columns.push({
337             id: "initiator",
338             title: WebInspector.NetworkLogView._columnTitles["initiator"],
339             sortable: true,
340             weight: 10
341         });
342
343         columns.push({
344             id: "cookies",
345             title: WebInspector.NetworkLogView._columnTitles["cookies"],
346             sortable: true,
347             weight: 6,
348             align: WebInspector.DataGrid.Align.Right
349         });
350
351         columns.push({
352             id: "setCookies",
353             title: WebInspector.NetworkLogView._columnTitles["setCookies"],
354             sortable: true,
355             weight: 6,
356             align: WebInspector.DataGrid.Align.Right
357         });
358
359         columns.push({
360             id: "size",
361             titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Content")),
362             title: WebInspector.NetworkLogView._columnTitles["size"],
363             sortable: true,
364             weight: 6,
365             align: WebInspector.DataGrid.Align.Right
366         });
367
368         columns.push({
369             id: "time",
370             titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Latency")),
371             title: WebInspector.NetworkLogView._columnTitles["time"],
372             sortable: true,
373             weight: 6,
374             align: WebInspector.DataGrid.Align.Right
375         });
376
377         columns.push({
378             id: "connectionId",
379             title: WebInspector.NetworkLogView._columnTitles["connectionId"],
380             sortable: true,
381             weight: 6
382         });
383
384         var responseHeaderColumns = WebInspector.NetworkLogView._responseHeaderColumns;
385         for (var i = 0; i < responseHeaderColumns.length; ++i) {
386             var headerName = responseHeaderColumns[i];
387             var descriptor = {
388                 id: headerName,
389                 title: WebInspector.NetworkLogView._columnTitles[headerName],
390                 weight: 6
391             }
392             if (headerName === "Content-Length")
393                 descriptor.align = WebInspector.DataGrid.Align.Right;
394             columns.push(descriptor);
395         }
396
397         columns.push({
398             id: "timeline",
399             titleDOMFragment: createDocumentFragment(),
400             title: WebInspector.NetworkLogView._columnTitles["timeline"],
401             sortable: false,
402             weight: 40,
403             sort: WebInspector.DataGrid.Order.Ascending
404         });
405
406         this._dataGrid = new WebInspector.SortableDataGrid(columns);
407         this._dataGrid.setStickToBottom(true);
408         this._updateColumns();
409         this._dataGrid.setName("networkLog");
410         this._dataGrid.setResizeMethod(WebInspector.DataGrid.ResizeMethod.Last);
411         this._dataGrid.element.classList.add("network-log-grid");
412         this._dataGrid.element.addEventListener("contextmenu", this._contextMenu.bind(this), true);
413         this._dataGrid.show(this.element);
414
415         // Event listeners need to be added _after_ we attach to the document, so that owner document is properly update.
416         this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortItems, this);
417         this._dataGrid.addEventListener(WebInspector.DataGrid.Events.ColumnsResized, this._updateDividersIfNeeded, this);
418
419         this._patchTimelineHeader();
420         this._dataGrid.sortNodes(this._sortingFunctions.startTime, false);
421     },
422
423     /**
424      * @param {string} title
425      * @param {string} subtitle
426      * @return {!DocumentFragment}
427      */
428     _makeHeaderFragment: function(title, subtitle)
429     {
430         var fragment = createDocumentFragment();
431         fragment.createTextChild(title);
432         var subtitleDiv = fragment.createChild("div", "network-header-subtitle");
433         subtitleDiv.createTextChild(subtitle);
434         return fragment;
435     },
436
437     _patchTimelineHeader: function()
438     {
439         var timelineSorting = createElement("select");
440
441         var option = createElement("option");
442         option.value = "startTime";
443         option.label = WebInspector.UIString("Timeline");
444         timelineSorting.appendChild(option);
445
446         option = createElement("option");
447         option.value = "startTime";
448         option.label = WebInspector.UIString("Start Time");
449         timelineSorting.appendChild(option);
450
451         option = createElement("option");
452         option.value = "responseTime";
453         option.label = WebInspector.UIString("Response Time");
454         timelineSorting.appendChild(option);
455
456         option = createElement("option");
457         option.value = "endTime";
458         option.label = WebInspector.UIString("End Time");
459         timelineSorting.appendChild(option);
460
461         option = createElement("option");
462         option.value = "duration";
463         option.label = WebInspector.UIString("Duration");
464         timelineSorting.appendChild(option);
465
466         option = createElement("option");
467         option.value = "latency";
468         option.label = WebInspector.UIString("Latency");
469         timelineSorting.appendChild(option);
470
471         var header = this._dataGrid.headerTableHeader("timeline");
472         header.replaceChild(timelineSorting, header.firstChild);
473
474         timelineSorting.addEventListener("click", function(event) { event.consume() }, false);
475         timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false);
476         this._timelineSortSelector = timelineSorting;
477     },
478
479     _createSortingFunctions: function()
480     {
481         this._sortingFunctions = {};
482         this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator;
483         this._sortingFunctions.method = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "method", false);
484         this._sortingFunctions.status = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "statusCode", false);
485         this._sortingFunctions.scheme = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "scheme", false);
486         this._sortingFunctions.domain = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "domain", false);
487         this._sortingFunctions.remoteAddress = WebInspector.NetworkDataGridNode.RemoteAddressComparator;
488         this._sortingFunctions.type = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "mimeType", false);
489         this._sortingFunctions.initiator = WebInspector.NetworkDataGridNode.InitiatorComparator;
490         this._sortingFunctions.cookies = WebInspector.NetworkDataGridNode.RequestCookiesCountComparator;
491         this._sortingFunctions.setCookies = WebInspector.NetworkDataGridNode.ResponseCookiesCountComparator;
492         this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator;
493         this._sortingFunctions.time = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "duration", false);
494         this._sortingFunctions.connectionId = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "connectionId", false);
495         this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "startTime", false);
496         this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "startTime", false);
497         this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "endTime", false);
498         this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "responseReceivedTime", false);
499         this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "duration", true);
500         this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "latency", true);
501     },
502
503     _createCalculators: function()
504     {
505         /** @type {!WebInspector.NetworkTransferTimeCalculator} */
506         this._timeCalculator = new WebInspector.NetworkTransferTimeCalculator();
507         /** @type {!WebInspector.NetworkTransferDurationCalculator} */
508         this._durationCalculator = new WebInspector.NetworkTransferDurationCalculator();
509
510         /** @type {!Object.<string, !WebInspector.NetworkTimeCalculator>} */
511         this._calculators = {};
512         this._calculators.timeline = this._timeCalculator;
513         this._calculators.startTime = this._timeCalculator;
514         this._calculators.endTime = this._timeCalculator;
515         this._calculators.responseTime = this._timeCalculator;
516         this._calculators.duration = this._durationCalculator;
517         this._calculators.latency = this._durationCalculator;
518
519         this._calculator = this._timeCalculator;
520     },
521
522     _sortItems: function()
523     {
524         this._removeAllNodeHighlights();
525         var columnIdentifier = this._dataGrid.sortColumnIdentifier();
526         if (columnIdentifier === "timeline") {
527             this._sortByTimeline();
528             return;
529         }
530         var sortingFunction = this._sortingFunctions[columnIdentifier];
531         if (!sortingFunction)
532             return;
533
534         this._dataGrid.sortNodes(sortingFunction, !this._dataGrid.isSortOrderAscending());
535         this._highlightNthMatchedRequestForSearch(this._updateMatchCountAndFindMatchIndex(this._currentMatchedRequestNode), false);
536         this._timelineSortSelector.selectedIndex = 0;
537
538         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
539             action: WebInspector.UserMetrics.UserActionNames.NetworkSort,
540             column: columnIdentifier,
541             sortOrder: this._dataGrid.sortOrder()
542         });
543     },
544
545     _sortByTimeline: function()
546     {
547         this._removeAllNodeHighlights();
548         var selectedIndex = this._timelineSortSelector.selectedIndex;
549         if (!selectedIndex)
550             selectedIndex = 1; // Sort by start time by default.
551         var selectedOption = this._timelineSortSelector[selectedIndex];
552         var value = selectedOption.value;
553
554         this._setCalculator(this._calculators[value]);
555         var sortingFunction = this._sortingFunctions[value];
556         this._dataGrid.sortNodes(sortingFunction);
557         this._highlightNthMatchedRequestForSearch(this._updateMatchCountAndFindMatchIndex(this._currentMatchedRequestNode), false);
558         this._dataGrid.markColumnAsSortedBy("timeline", WebInspector.DataGrid.Order.Ascending);
559     },
560
561     _createStatusBarItems: function()
562     {
563         this._progressBarContainer = createElement("div");
564         this._progressBarContainer.className = "status-bar-item";
565     },
566
567     _updateSummaryBar: function()
568     {
569         var requestsNumber = this._nodesByRequestId.size;
570
571         if (!requestsNumber) {
572             if (this._summaryBarElement._isDisplayingWarning)
573                 return;
574             this._summaryBarElement._isDisplayingWarning = true;
575             this._summaryBarElement.removeChildren();
576             this._summaryBarElement.createChild("div", "warning-icon-small");
577             var text = WebInspector.UIString("No requests captured. Reload the page to see detailed information on the network activity.");
578             this._summaryBarElement.createTextChild(text);
579             this._summaryBarElement.title = text;
580             return;
581         }
582         delete this._summaryBarElement._isDisplayingWarning;
583
584         var transferSize = 0;
585         var selectedRequestsNumber = 0;
586         var selectedTransferSize = 0;
587         var baseTime = -1;
588         var maxTime = -1;
589         var nodes = this._nodesByRequestId.valuesArray();
590         for (var i = 0; i < nodes.length; ++i) {
591             var request = nodes[i].request();
592             var requestTransferSize = request.transferSize;
593             transferSize += requestTransferSize;
594             if (!nodes[i]._isFilteredOut) {
595                 selectedRequestsNumber++;
596                 selectedTransferSize += requestTransferSize;
597             }
598             if (request.url === request.target().resourceTreeModel.inspectedPageURL())
599                 baseTime = request.startTime;
600             if (request.endTime > maxTime)
601                 maxTime = request.endTime;
602         }
603         var text = "";
604         if (selectedRequestsNumber !== requestsNumber) {
605             text += String.sprintf(WebInspector.UIString("%d / %d requests"), selectedRequestsNumber, requestsNumber);
606             text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s / %s transferred"), Number.bytesToString(selectedTransferSize), Number.bytesToString(transferSize));
607         } else {
608             text += String.sprintf(WebInspector.UIString("%d requests"), requestsNumber);
609             text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize));
610         }
611         if (baseTime !== -1 && this._mainRequestLoadTime !== -1 && this._mainRequestDOMContentLoadedTime !== -1 && this._mainRequestDOMContentLoadedTime > baseTime) {
612             text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s (load: %s, DOMContentLoaded: %s)"),
613                         Number.secondsToString(maxTime - baseTime),
614                         Number.secondsToString(this._mainRequestLoadTime - baseTime),
615                         Number.secondsToString(this._mainRequestDOMContentLoadedTime - baseTime));
616         }
617         this._summaryBarElement.textContent = text;
618         this._summaryBarElement.title = text;
619     },
620
621     _scheduleRefresh: function()
622     {
623         if (this._needsRefresh)
624             return;
625
626         this._needsRefresh = true;
627
628         if (this.isShowing() && !this._refreshTimeout)
629             this._refreshTimeout = setTimeout(this.refresh.bind(this), WebInspector.NetworkLogView._defaultRefreshDelay);
630     },
631
632     _updateDividersIfNeeded: function()
633     {
634         var timelineOffset = this._dataGrid.columnOffset("timeline");
635         // Position timline grid location.
636         if (timelineOffset)
637             this._timelineGrid.element.style.left = timelineOffset + "px";
638
639         var calculator = this.calculator();
640         var proceed = true;
641         if (!this.isShowing()) {
642             this._scheduleRefresh();
643             proceed = false;
644         } else {
645             calculator.setDisplayWindow(this._timelineGrid.dividersElement.clientWidth);
646             proceed = this._timelineGrid.updateDividers(calculator);
647         }
648         if (!proceed)
649             return;
650
651         if (calculator.startAtZero) {
652             // If our current sorting method starts at zero, that means it shows all
653             // requests starting at the same point, and so onLoad event and DOMContent
654             // event lines really wouldn't make much sense here, so don't render them.
655             return;
656         }
657
658         this._timelineGrid.removeEventDividers();
659         if (this._mainRequestLoadTime !== -1) {
660             var percent = calculator.computePercentageFromEventTime(this._mainRequestLoadTime);
661
662             var loadDivider = createElement("div");
663             loadDivider.className = "network-event-divider network-red-divider";
664
665             var loadDividerPadding = createElement("div");
666             loadDividerPadding.className = "network-event-divider-padding";
667             loadDividerPadding.title = WebInspector.UIString("Load event");
668             loadDividerPadding.appendChild(loadDivider);
669             loadDividerPadding.style.left = percent + "%";
670             this._timelineGrid.addEventDivider(loadDividerPadding);
671         }
672
673         if (this._mainRequestDOMContentLoadedTime !== -1) {
674             var percent = calculator.computePercentageFromEventTime(this._mainRequestDOMContentLoadedTime);
675
676             var domContentLoadedDivider = createElement("div");
677             domContentLoadedDivider.className = "network-event-divider network-blue-divider";
678
679             var domContentLoadedDividerPadding = createElement("div");
680             domContentLoadedDividerPadding.className = "network-event-divider-padding";
681             domContentLoadedDividerPadding.title = WebInspector.UIString("DOMContentLoaded event");
682             domContentLoadedDividerPadding.appendChild(domContentLoadedDivider);
683             domContentLoadedDividerPadding.style.left = percent + "%";
684             this._timelineGrid.addEventDivider(domContentLoadedDividerPadding);
685         }
686     },
687
688     _refreshIfNeeded: function()
689     {
690         if (this._needsRefresh)
691             this.refresh();
692     },
693
694     _invalidateAllItems: function()
695     {
696         var requestIds = this._nodesByRequestId.keysArray();
697         for (var i = 0; i < requestIds.length; ++i)
698             this._staleRequestIds[requestIds[i]] = true;
699     },
700
701     /**
702      * @return {!WebInspector.NetworkTimeCalculator}
703      */
704     calculator: function()
705     {
706         return this._calculator;
707     },
708
709     /**
710      * @param {!WebInspector.NetworkTimeCalculator} x
711      */
712     _setCalculator: function(x)
713     {
714         if (!x || this._calculator === x)
715             return;
716
717         this._calculator = x;
718         this._calculator.reset();
719
720         if (this._calculator.startAtZero)
721             this._timelineGrid.hideEventDividers();
722         else
723             this._timelineGrid.showEventDividers();
724
725         this._invalidateAllItems();
726         this.refresh();
727     },
728
729     _createStatusbarButtons: function()
730     {
731         this._recordButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item");
732         this._recordButton.addEventListener("click", this._onRecordButtonClicked, this);
733
734         this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
735         this._clearButton.addEventListener("click", this._reset, this);
736
737         this._largerRequestsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item");
738         this._largerRequestsButton.toggled = WebInspector.settings.resourcesLargeRows.get();
739         this._largerRequestsButton.addEventListener("click", this._toggleLargerRequests, this);
740
741         this._preserveLogCheckbox = new WebInspector.StatusBarCheckbox(WebInspector.UIString("Preserve log"));
742         this._preserveLogCheckbox.element.title = WebInspector.UIString("Do not clear log on page reload / navigation.");
743
744         this._disableCacheCheckbox = new WebInspector.StatusBarCheckbox(WebInspector.UIString("Disable cache"));
745         WebInspector.SettingsUI.bindCheckbox(this._disableCacheCheckbox.inputElement, WebInspector.settings.cacheDisabled);
746         this._disableCacheCheckbox.element.title = WebInspector.UIString("Disable cache (while DevTools is open).");
747     },
748
749     /**
750      * @param {!WebInspector.Event} event
751      */
752     _loadEventFired: function(event)
753     {
754         if (!this._recordButton.toggled)
755             return;
756
757         var data = /** @type {number} */ (event.data);
758         this._mainRequestLoadTime = data || -1;
759         // Schedule refresh to update boundaries and draw the new line.
760         this._scheduleRefresh();
761     },
762
763     /**
764      * @param {!WebInspector.Event} event
765      */
766     _domContentLoadedEventFired: function(event)
767     {
768         if (!this._recordButton.toggled)
769             return;
770         var data = /** @type {number} */ (event.data);
771         this._mainRequestDOMContentLoadedTime = data || -1;
772         // Schedule refresh to update boundaries and draw the new line.
773         this._scheduleRefresh();
774     },
775
776     wasShown: function()
777     {
778         this._refreshIfNeeded();
779     },
780
781     willHide: function()
782     {
783         this._popoverHelper.hidePopover();
784     },
785
786     refresh: function()
787     {
788         this._needsRefresh = false;
789         if (this._refreshTimeout) {
790             clearTimeout(this._refreshTimeout);
791             delete this._refreshTimeout;
792         }
793
794         this._removeAllNodeHighlights();
795         var boundariesChanged = false;
796         var calculator = this.calculator();
797         if (calculator.updateBoundariesForEventTime) {
798             boundariesChanged = calculator.updateBoundariesForEventTime(this._mainRequestLoadTime) || boundariesChanged;
799             boundariesChanged = calculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime) || boundariesChanged;
800         }
801
802         var dataGrid = this._dataGrid;
803         var rootNode = dataGrid.rootNode();
804         var nodesToInsert = [];
805         for (var requestId in this._staleRequestIds) {
806             var node = this._nodesByRequestId.get(requestId);
807             if (!node)
808                 continue;
809             if (!node._isFilteredOut)
810                 rootNode.removeChild(node);
811             node._isFilteredOut = !this._applyFilter(node);
812             if (!node._isFilteredOut)
813                 nodesToInsert.push(node);
814         }
815
816         for (var i = 0; i < nodesToInsert.length; ++i) {
817             var node = nodesToInsert[i];
818             var request = node.request();
819             node.refresh();
820             dataGrid.insertChild(node);
821             node._isMatchingSearchQuery = this._matchRequest(request);
822             if (calculator.updateBoundaries(request))
823                 boundariesChanged = true;
824         }
825
826         this._highlightNthMatchedRequestForSearch(this._updateMatchCountAndFindMatchIndex(this._currentMatchedRequestNode), false);
827
828         if (boundariesChanged) {
829             // The boundaries changed, so all item graphs are stale.
830             this._updateDividersIfNeeded();
831             var nodes = this._nodesByRequestId.valuesArray();
832             for (var i = 0; i < nodes.length; ++i)
833                 nodes[i].refreshGraph();
834         }
835
836         this._staleRequestIds = {};
837         this._updateSummaryBar();
838     },
839
840     _onRecordButtonClicked: function()
841     {
842         if (!this._recordButton.toggled)
843             this._reset();
844         this._toggleRecordButton(!this._recordButton.toggled);
845     },
846
847     /**
848      * @param {boolean} toggled
849      */
850     _toggleRecordButton: function(toggled)
851     {
852         this._recordButton.toggled = toggled;
853         this._recordButton.title = toggled ? WebInspector.UIString("Stop Recording Network Log") : WebInspector.UIString("Record Network Log");
854     },
855
856     _reset: function()
857     {
858         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ViewCleared);
859
860         this._clearSearchMatchedList();
861         if (this._popoverHelper)
862             this._popoverHelper.hidePopover();
863
864         if (this._calculator)
865             this._calculator.reset();
866
867         var nodes = this._nodesByRequestId.valuesArray();
868         for (var i = 0; i < nodes.length; ++i)
869             nodes[i].dispose();
870
871         this._nodesByRequestId.clear();
872         this._staleRequestIds = {};
873         this._resetSuggestionBuilder();
874
875         if (this._dataGrid) {
876             this._dataGrid.rootNode().removeChildren();
877             this._updateDividersIfNeeded();
878             this._updateSummaryBar();
879         }
880
881         this._mainRequestLoadTime = -1;
882         this._mainRequestDOMContentLoadedTime = -1;
883     },
884
885     /**
886      * @param {!WebInspector.Event} event
887      */
888     _onRequestStarted: function(event)
889     {
890         if (this._recordButton.toggled) {
891             var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
892             this._appendRequest(request);
893         }
894     },
895
896     /**
897      * @param {!WebInspector.NetworkRequest} request
898      */
899     _appendRequest: function(request)
900     {
901         var node = new WebInspector.NetworkDataGridNode(this, request);
902
903         // In case of redirect request id is reassigned to a redirected
904         // request and we need to update _nodesByRequestId and search results.
905         var originalRequestNode = this._nodesByRequestId.get(request.requestId);
906         if (originalRequestNode)
907             this._nodesByRequestId.set(originalRequestNode.request().requestId, originalRequestNode);
908         this._nodesByRequestId.set(request.requestId, node);
909
910         // Pull all the redirects of the main request upon commit load.
911         if (request.redirects) {
912             for (var i = 0; i < request.redirects.length; ++i)
913                 this._refreshRequest(request.redirects[i]);
914         }
915
916         this._refreshRequest(request);
917     },
918
919     /**
920      * @param {!WebInspector.Event} event
921      */
922     _onRequestUpdated: function(event)
923     {
924         var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
925         this._refreshRequest(request);
926     },
927
928     /**
929      * @param {!WebInspector.NetworkRequest} request
930      */
931     _refreshRequest: function(request)
932     {
933         if (!this._nodesByRequestId.get(request.requestId))
934             return;
935
936         this._suggestionBuilder.addItem(WebInspector.NetworkLogView.FilterType.Domain, request.domain);
937         this._suggestionBuilder.addItem(WebInspector.NetworkLogView.FilterType.Method, request.requestMethod);
938         this._suggestionBuilder.addItem(WebInspector.NetworkLogView.FilterType.MimeType, request.mimeType);
939         this._suggestionBuilder.addItem(WebInspector.NetworkLogView.FilterType.Scheme, "" + request.scheme);
940         this._suggestionBuilder.addItem(WebInspector.NetworkLogView.FilterType.StatusCode, "" + request.statusCode);
941
942         var responseHeaders = request.responseHeaders;
943         for (var i = 0, l = responseHeaders.length; i < l; ++i)
944             this._suggestionBuilder.addItem(WebInspector.NetworkLogView.FilterType.HasResponseHeader, responseHeaders[i].name);
945         var cookies = request.responseCookies;
946         for (var i = 0, l = cookies ? cookies.length : 0; i < l; ++i) {
947             var cookie = cookies[i];
948             this._suggestionBuilder.addItem(WebInspector.NetworkLogView.FilterType.SetCookieDomain, cookie.domain());
949             this._suggestionBuilder.addItem(WebInspector.NetworkLogView.FilterType.SetCookieName, cookie.name());
950             this._suggestionBuilder.addItem(WebInspector.NetworkLogView.FilterType.SetCookieValue, cookie.value());
951         }
952
953         this._staleRequestIds[request.requestId] = true;
954         this._scheduleRefresh();
955     },
956
957     /**
958      * @param {!WebInspector.Event} event
959      */
960     _willReloadPage: function(event)
961     {
962         this._recordButton.toggled = true;
963         if (!this._preserveLogCheckbox.checked())
964             this._reset();
965     },
966
967     /**
968      * @param {!WebInspector.Event} event
969      */
970     _mainFrameNavigated: function(event)
971     {
972         if (!this._recordButton.toggled || this._preserveLogCheckbox.checked())
973             return;
974
975         var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
976         var loaderId = frame.loaderId;
977
978         // Pick provisional load requests.
979         var requestsToPick = [];
980         var requests = frame.target().networkLog.requests;
981         for (var i = 0; i < requests.length; ++i) {
982             var request = requests[i];
983             if (request.loaderId === loaderId)
984                 requestsToPick.push(request);
985         }
986
987         this._reset();
988
989         for (var i = 0; i < requestsToPick.length; ++i)
990             this._appendRequest(requestsToPick[i]);
991     },
992
993     /**
994      * @param {boolean} detailed
995      */
996     switchViewMode: function(detailed)
997     {
998         if (this._detailedMode === detailed)
999             return;
1000         this._detailedMode = detailed;
1001
1002         if (detailed) {
1003             if (this._dataGrid.selectedNode)
1004                 this._dataGrid.selectedNode.selected = false;
1005         } else {
1006             this._removeAllNodeHighlights();
1007             this._popoverHelper.hidePopover();
1008         }
1009
1010         this.element.classList.toggle("brief-mode", !detailed);
1011         this._updateColumns();
1012     },
1013
1014     _toggleLargerRequests: function()
1015     {
1016         WebInspector.settings.resourcesLargeRows.set(!WebInspector.settings.resourcesLargeRows.get());
1017         this._updateRowsSize();
1018     },
1019
1020     /**
1021      * @return {number}
1022      */
1023     rowHeight: function()
1024     {
1025         return this._rowHeight;
1026     },
1027
1028     _updateRowsSize: function()
1029     {
1030         var largeRows = this.usesLargeRows();
1031         this._largerRequestsButton.toggled = largeRows;
1032         this._rowHeight = largeRows ? 41 : 21;
1033         this._largerRequestsButton.title = WebInspector.UIString(largeRows ? "Use small resource rows." : "Use large resource rows.");
1034         this._dataGrid.element.classList.toggle("small", !largeRows);
1035         this._timelineGrid.element.classList.toggle("small", !largeRows);
1036         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, { largeRows: largeRows });
1037     },
1038
1039     /**
1040      * @param {!Element} element
1041      * @param {!Event} event
1042      * @return {!Element|!AnchorBox|undefined}
1043      */
1044     _getPopoverAnchor: function(element, event)
1045     {
1046         if (!this._allowPopover)
1047             return;
1048         var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label");
1049         if (anchor && anchor.parentElement.request && anchor.parentElement.request.timing)
1050             return anchor;
1051         anchor = element.enclosingNodeOrSelfWithClass("network-script-initiated");
1052         if (anchor && anchor.request) {
1053             var request = /** @type {!WebInspector.NetworkRequest} */ (anchor.request);
1054             var initiator = anchor.request.initiator();
1055             if (initiator && (initiator.stackTrace || initiator.asyncStackTrace))
1056                 return anchor;
1057         }
1058     },
1059
1060     /**
1061      * @param {!Element} anchor
1062      * @param {!WebInspector.Popover} popover
1063      */
1064     _showPopover: function(anchor, popover)
1065     {
1066         var content;
1067         if (anchor.classList.contains("network-script-initiated")) {
1068             content = this._generateScriptInitiatedPopoverContent(anchor.request);
1069             popover.setCanShrink(true);
1070         } else {
1071             content = WebInspector.RequestTimingView.createTimingTable(anchor.parentElement.request);
1072             popover.setCanShrink(false);
1073         }
1074         popover.show(content, anchor);
1075     },
1076
1077     _onHidePopover: function()
1078     {
1079         this._linkifier.reset();
1080     },
1081
1082     /**
1083      * @param {!WebInspector.NetworkRequest} request
1084      * @return {!Element}
1085      */
1086     _generateScriptInitiatedPopoverContent: function(request)
1087     {
1088         var framesTable = createElementWithClass("table", "network-stack-trace");
1089
1090         /**
1091          * @param {!Array.<!ConsoleAgent.CallFrame>} stackTrace
1092          * @this {WebInspector.NetworkLogView}
1093          */
1094         function appendStackTrace(stackTrace)
1095         {
1096             for (var i = 0; i < stackTrace.length; ++i) {
1097                 var stackFrame = stackTrace[i];
1098                 var row = createElement("tr");
1099                 row.createChild("td").textContent = stackFrame.functionName || WebInspector.UIString("(anonymous function)");
1100                 row.createChild("td").textContent = " @ ";
1101                 row.createChild("td").appendChild(this._linkifier.linkifyConsoleCallFrame(request.target(), stackFrame));
1102                 framesTable.appendChild(row);
1103             }
1104         }
1105
1106         // Initiator is not null, checked in _getPopoverAnchor.
1107         var initiator = /** @type {!NetworkAgent.Initiator} */ (request.initiator());
1108         if (initiator.stackTrace)
1109             appendStackTrace.call(this, initiator.stackTrace);
1110
1111         var asyncStackTrace = initiator.asyncStackTrace;
1112         while (asyncStackTrace) {
1113             var callFrames = asyncStackTrace.callFrames;
1114             if (!callFrames || !callFrames.length)
1115                 break;
1116             var row = framesTable.createChild("tr");
1117             row.createChild("td", "network-async-trace-description").textContent = WebInspector.asyncStackTraceLabel(asyncStackTrace.description);
1118             row.createChild("td");
1119             row.createChild("td");
1120             appendStackTrace.call(this, callFrames);
1121             asyncStackTrace = asyncStackTrace.asyncStackTrace;
1122         }
1123
1124         return framesTable;
1125     },
1126
1127     _updateColumns: function()
1128     {
1129         var detailedMode = !!this._detailedMode;
1130         var visibleColumns = {"name": true};
1131         if (detailedMode) {
1132             visibleColumns["timeline"] = true;
1133             var columnsVisibility = this._coulmnsVisibilitySetting.get();
1134             for (var columnIdentifier in columnsVisibility)
1135                 visibleColumns[columnIdentifier] = columnsVisibility[columnIdentifier];
1136         }
1137
1138         this._dataGrid.setColumnsVisiblity(visibleColumns);
1139     },
1140
1141     /**
1142      * @param {string} columnIdentifier
1143      */
1144     _toggleColumnVisibility: function(columnIdentifier)
1145     {
1146         var columnsVisibility = this._coulmnsVisibilitySetting.get();
1147         columnsVisibility[columnIdentifier] = !columnsVisibility[columnIdentifier];
1148         this._coulmnsVisibilitySetting.set(columnsVisibility);
1149
1150         this._updateColumns();
1151     },
1152
1153     /**
1154      * @return {!Array.<string>}
1155      */
1156     _getConfigurableColumnIDs: function()
1157     {
1158         if (this._configurableColumnIDs)
1159             return this._configurableColumnIDs;
1160
1161         var columnTitles = WebInspector.NetworkLogView._columnTitles;
1162         function compare(id1, id2)
1163         {
1164             return columnTitles[id1].compareTo(columnTitles[id2]);
1165         }
1166
1167         var columnIDs = Object.keys(this._coulmnsVisibilitySetting.get());
1168         this._configurableColumnIDs = columnIDs.sort(compare);
1169         return this._configurableColumnIDs;
1170     },
1171
1172     /**
1173      * @param {!Event} event
1174      */
1175     _contextMenu: function(event)
1176     {
1177         var contextMenu = new WebInspector.ContextMenu(event);
1178
1179         if (this._detailedMode && event.target.isSelfOrDescendant(this._dataGrid.headerTableBody)) {
1180             var columnsVisibility = this._coulmnsVisibilitySetting.get();
1181             var columnIDs = this._getConfigurableColumnIDs();
1182             var columnTitles = WebInspector.NetworkLogView._columnTitles;
1183             for (var i = 0; i < columnIDs.length; ++i) {
1184                 var columnIdentifier = columnIDs[i];
1185                 contextMenu.appendCheckboxItem(columnTitles[columnIdentifier], this._toggleColumnVisibility.bind(this, columnIdentifier), !!columnsVisibility[columnIdentifier]);
1186             }
1187             contextMenu.show();
1188             return;
1189         }
1190
1191         var gridNode = this._dataGrid.dataGridNodeFromNode(event.target);
1192         var request = gridNode && gridNode.request();
1193
1194         /**
1195          * @param {string} url
1196          */
1197         function openResourceInNewTab(url)
1198         {
1199             InspectorFrontendHost.openInNewTab(url);
1200         }
1201
1202         if (request) {
1203             contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), openResourceInNewTab.bind(null, request.url));
1204             contextMenu.appendSeparator();
1205             contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), this._copyLocation.bind(this, request));
1206             if (request.requestHeadersText())
1207                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy request headers" : "Copy Request Headers"), this._copyRequestHeaders.bind(this, request));
1208             if (request.responseHeadersText)
1209                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy response headers" : "Copy Response Headers"), this._copyResponseHeaders.bind(this, request));
1210             if (request.finished)
1211                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy response" : "Copy Response"), this._copyResponse.bind(this, request));
1212             contextMenu.appendItem(WebInspector.UIString("Copy as cURL"), this._copyCurlCommand.bind(this, request));
1213         }
1214         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy all as HAR" : "Copy All as HAR"), this._copyAll.bind(this));
1215
1216         contextMenu.appendSeparator();
1217         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as HAR with content" : "Save as HAR with Content"), this._exportAll.bind(this));
1218
1219         contextMenu.appendSeparator();
1220         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cache" : "Clear Browser Cache"), this._clearBrowserCache.bind(this));
1221         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cookies" : "Clear Browser Cookies"), this._clearBrowserCookies.bind(this));
1222
1223         if (request && request.resourceType() === WebInspector.resourceTypes.XHR) {
1224             contextMenu.appendSeparator();
1225             contextMenu.appendItem(WebInspector.UIString("Replay XHR"), request.replayXHR.bind(request));
1226             contextMenu.appendSeparator();
1227         }
1228
1229         contextMenu.show();
1230     },
1231
1232     _harRequests: function()
1233     {
1234         var requests = this._nodesByRequestId.valuesArray().map(function(node) { return node.request(); });
1235         var httpRequests = requests.filter(WebInspector.NetworkLogView.HTTPRequestsFilter);
1236         httpRequests = httpRequests.filter(WebInspector.NetworkLogView.FinishedRequestsFilter);
1237         return httpRequests.filter(WebInspector.NetworkLogView.NonDevToolsRequestsFilter);
1238     },
1239
1240     _copyAll: function()
1241     {
1242         var harArchive = {
1243             log: (new WebInspector.HARLog(this._harRequests())).build()
1244         };
1245         InspectorFrontendHost.copyText(JSON.stringify(harArchive, null, 2));
1246     },
1247
1248     /**
1249      * @param {!WebInspector.NetworkRequest} request
1250      */
1251     _copyLocation: function(request)
1252     {
1253         InspectorFrontendHost.copyText(request.url);
1254     },
1255
1256     /**
1257      * @param {!WebInspector.NetworkRequest} request
1258      */
1259     _copyRequestHeaders: function(request)
1260     {
1261         InspectorFrontendHost.copyText(request.requestHeadersText());
1262     },
1263
1264     /**
1265      * @param {!WebInspector.NetworkRequest} request
1266      */
1267     _copyResponse: function(request)
1268     {
1269         /**
1270          * @param {?string} content
1271          */
1272         function callback(content)
1273         {
1274             if (request.contentEncoded)
1275                 content = request.asDataURL();
1276             InspectorFrontendHost.copyText(content || "");
1277         }
1278         request.requestContent(callback);
1279     },
1280
1281     /**
1282      * @param {!WebInspector.NetworkRequest} request
1283      */
1284     _copyResponseHeaders: function(request)
1285     {
1286         InspectorFrontendHost.copyText(request.responseHeadersText);
1287     },
1288
1289     /**
1290      * @param {!WebInspector.NetworkRequest} request
1291      */
1292     _copyCurlCommand: function(request)
1293     {
1294         InspectorFrontendHost.copyText(this._generateCurlCommand(request));
1295     },
1296
1297     _exportAll: function()
1298     {
1299         var filename = WebInspector.targetManager.inspectedPageDomain() + ".har";
1300         var stream = new WebInspector.FileOutputStream();
1301         stream.open(filename, openCallback.bind(this));
1302
1303         /**
1304          * @param {boolean} accepted
1305          * @this {WebInspector.NetworkLogView}
1306          */
1307         function openCallback(accepted)
1308         {
1309             if (!accepted)
1310                 return;
1311             var progressIndicator = new WebInspector.ProgressIndicator();
1312             this._progressBarContainer.appendChild(progressIndicator.element);
1313             var harWriter = new WebInspector.HARWriter();
1314             harWriter.write(stream, this._harRequests(), progressIndicator);
1315         }
1316     },
1317
1318     _clearBrowserCache: function()
1319     {
1320         if (confirm(WebInspector.UIString("Are you sure you want to clear browser cache?")))
1321             NetworkAgent.clearBrowserCache();
1322     },
1323
1324     _clearBrowserCookies: function()
1325     {
1326         if (confirm(WebInspector.UIString("Are you sure you want to clear browser cookies?")))
1327             NetworkAgent.clearBrowserCookies();
1328     },
1329
1330     /**
1331      * @param {!WebInspector.NetworkRequest} request
1332      * @return {boolean}
1333      */
1334     _matchRequest: function(request)
1335     {
1336         var re = this._searchRegExp;
1337         if (!re)
1338             return false;
1339         return re.test(request.name()) || re.test(request.path());
1340     },
1341
1342     _clearSearchMatchedList: function()
1343     {
1344         this._matchedRequestCount = -1;
1345         this._currentMatchedRequestNode = null;
1346         this._removeAllHighlights();
1347     },
1348
1349     _removeAllHighlights: function()
1350     {
1351         this._removeAllNodeHighlights();
1352         for (var i = 0; i < this._highlightedSubstringChanges.length; ++i)
1353             WebInspector.revertDomChanges(this._highlightedSubstringChanges[i]);
1354         this._highlightedSubstringChanges = [];
1355     },
1356
1357     /**
1358      * @param {number} n
1359      * @param {boolean} reveal
1360      */
1361     _highlightNthMatchedRequestForSearch: function(n, reveal)
1362     {
1363         this._removeAllHighlights();
1364
1365         /** @type {!Array.<!WebInspector.NetworkDataGridNode>} */
1366         var nodes = this._dataGrid.rootNode().children;
1367         var matchCount = 0;
1368         var node = null;
1369         for (var i = 0; i < nodes.length; ++i) {
1370             if (nodes[i]._isMatchingSearchQuery) {
1371                 if (matchCount === n) {
1372                     node = nodes[i];
1373                     break;
1374                 }
1375                 matchCount++;
1376             }
1377         }
1378         if (!node) {
1379             this._currentMatchedRequestNode = null;
1380             return;
1381         }
1382
1383         var request = node.request();
1384         var regExp = this._searchRegExp;
1385         var nameMatched = request.name().match(regExp);
1386         var pathMatched = request.path().match(regExp);
1387         if (!nameMatched && pathMatched && !this._largerRequestsButton.toggled)
1388             this._toggleLargerRequests();
1389         if (reveal)
1390             WebInspector.Revealer.reveal(request);
1391         var highlightedSubstringChanges = node.highlightMatchedSubstring(regExp);
1392         this._highlightedSubstringChanges.push(highlightedSubstringChanges);
1393
1394         this._currentMatchedRequestNode = node;
1395         this._currentMatchedRequestIndex = n;
1396         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, n);
1397     },
1398
1399     /**
1400      * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
1401      * @param {boolean} shouldJump
1402      * @param {boolean=} jumpBackwards
1403      */
1404     performSearch: function(searchConfig, shouldJump, jumpBackwards)
1405     {
1406         var query = searchConfig.query;
1407         var currentMatchedRequestNode = this._currentMatchedRequestNode;
1408         this._clearSearchMatchedList();
1409         this._searchRegExp = createPlainTextSearchRegex(query, "i");
1410
1411         /** @type {!Array.<!WebInspector.NetworkDataGridNode>} */
1412         var nodes = this._dataGrid.rootNode().children;
1413         for (var i = 0; i < nodes.length; ++i)
1414             nodes[i]._isMatchingSearchQuery = this._matchRequest(nodes[i].request());
1415         var newMatchedRequestIndex = this._updateMatchCountAndFindMatchIndex(currentMatchedRequestNode);
1416         if (!newMatchedRequestIndex && jumpBackwards)
1417             newMatchedRequestIndex = this._matchedRequestCount - 1;
1418         this._highlightNthMatchedRequestForSearch(newMatchedRequestIndex, shouldJump);
1419     },
1420
1421     /**
1422      * @return {boolean}
1423      */
1424     supportsCaseSensitiveSearch: function()
1425     {
1426         return false;
1427     },
1428
1429     /**
1430      * @return {boolean}
1431      */
1432     supportsRegexSearch: function()
1433     {
1434         return false;
1435     },
1436
1437     /**
1438      * @param {?WebInspector.NetworkDataGridNode} node
1439      * @return {number}
1440      */
1441     _updateMatchCountAndFindMatchIndex: function(node)
1442     {
1443         /** @type {!Array.<!WebInspector.NetworkDataGridNode>} */
1444         var nodes = this._dataGrid.rootNode().children;
1445         var matchCount = 0;
1446         var matchIndex = 0;
1447         for (var i = 0; i < nodes.length; ++i) {
1448             if (!nodes[i]._isMatchingSearchQuery)
1449                 continue;
1450             if (node === nodes[i])
1451                 matchIndex = matchCount;
1452             matchCount++;
1453         }
1454         if (this._matchedRequestCount !== matchCount) {
1455             this._matchedRequestCount = matchCount;
1456             this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, matchCount);
1457         }
1458         return matchIndex;
1459     },
1460
1461     /**
1462      * @param {number} index
1463      * @return {number}
1464      */
1465     _normalizeSearchResultIndex: function(index)
1466     {
1467         return (index + this._matchedRequestCount) % this._matchedRequestCount;
1468     },
1469
1470     /**
1471      * @param {!WebInspector.NetworkDataGridNode} node
1472      * @return {boolean}
1473      */
1474     _applyFilter: function(node)
1475     {
1476         var request = node.request();
1477         var resourceType = request.resourceType();
1478         if (resourceType === WebInspector.resourceTypes.TextTrack)
1479             resourceType = WebInspector.resourceTypes.Other;
1480         if (!this._resourceTypeFilterUI.accept(resourceType.name()))
1481             return false;
1482         if (this._dataURLFilterUI.checked() && request.parsedURL.isDataURL())
1483             return false;
1484         for (var i = 0; i < this._filters.length; ++i) {
1485             if (!this._filters[i](request))
1486                 return false;
1487         }
1488         return true;
1489     },
1490
1491     /**
1492      * @param {string} query
1493      */
1494     _parseFilterQuery: function(query)
1495     {
1496         var parsedQuery = this._suggestionBuilder.parseQuery(query);
1497         this._filters = parsedQuery.text.map(this._createTextFilter);
1498         var filters = parsedQuery.filters;
1499         var n = parsedQuery.filters.length;
1500         for (var i = 0; i < n; ++i) {
1501             var filter = parsedQuery.filters[i];
1502             var filterType = /** @type {!WebInspector.NetworkLogView.FilterType} */ (filter.type);
1503             this._filters.push(this._createFilter(filterType, filter.data, filter.negative));
1504         }
1505     },
1506
1507     /**
1508      * @param {string} text
1509      * @return {!WebInspector.NetworkLogView.Filter}
1510      */
1511     _createTextFilter: function(text)
1512     {
1513         var regexp = new RegExp(text.escapeForRegExp(), "i");
1514         return WebInspector.NetworkLogView._requestNameOrPathFilter.bind(null, regexp);
1515     },
1516
1517     /**
1518      * @param {!WebInspector.NetworkLogView.FilterType} type
1519      * @param {string} value
1520      * @param {boolean} negative
1521      * @return {!WebInspector.NetworkLogView.Filter}
1522      */
1523     _createFilter: function(type, value, negative)
1524     {
1525         var filter = this._createSpecialFilter(type, value);
1526         if (!filter)
1527             return this._createTextFilter((negative ? "-" : "") + type + ":" + value);
1528         if (negative)
1529             return WebInspector.NetworkLogView._negativeFilter.bind(null, filter);
1530         return filter;
1531     },
1532
1533     /**
1534      * @param {!WebInspector.NetworkLogView.FilterType} type
1535      * @param {string} value
1536      * @return {?WebInspector.NetworkLogView.Filter}
1537      */
1538     _createSpecialFilter: function(type, value)
1539     {
1540         switch (type) {
1541         case WebInspector.NetworkLogView.FilterType.Domain:
1542             return WebInspector.NetworkLogView._requestDomainFilter.bind(null, value);
1543
1544         case WebInspector.NetworkLogView.FilterType.HasResponseHeader:
1545             return WebInspector.NetworkLogView._requestResponseHeaderFilter.bind(null, value);
1546
1547         case WebInspector.NetworkLogView.FilterType.Is:
1548             if (value.toLowerCase() === WebInspector.NetworkLogView.IsFilterType.Running)
1549                 return WebInspector.NetworkLogView._runningRequestFilter;
1550             break;
1551
1552         case WebInspector.NetworkLogView.FilterType.Method:
1553             return WebInspector.NetworkLogView._requestMethodFilter.bind(null, value);
1554
1555         case WebInspector.NetworkLogView.FilterType.MimeType:
1556             return WebInspector.NetworkLogView._requestMimeTypeFilter.bind(null, value);
1557
1558         case WebInspector.NetworkLogView.FilterType.Scheme:
1559             return WebInspector.NetworkLogView._requestSchemeFilter.bind(null, value);
1560
1561         case WebInspector.NetworkLogView.FilterType.SetCookieDomain:
1562             return WebInspector.NetworkLogView._requestSetCookieDomainFilter.bind(null, value);
1563
1564         case WebInspector.NetworkLogView.FilterType.SetCookieName:
1565             return WebInspector.NetworkLogView._requestSetCookieNameFilter.bind(null, value);
1566
1567         case WebInspector.NetworkLogView.FilterType.SetCookieValue:
1568             return WebInspector.NetworkLogView._requestSetCookieValueFilter.bind(null, value);
1569
1570         case WebInspector.NetworkLogView.FilterType.StatusCode:
1571             return WebInspector.NetworkLogView._statusCodeFilter.bind(null, value);
1572         }
1573         return null;
1574     },
1575
1576     _filterRequests: function()
1577     {
1578         this._removeAllHighlights();
1579         this._invalidateAllItems();
1580         this.refresh();
1581     },
1582
1583     jumpToPreviousSearchResult: function()
1584     {
1585         if (!this._matchedRequestCount)
1586             return;
1587         var index = this._normalizeSearchResultIndex(this._currentMatchedRequestIndex - 1);
1588         this._highlightNthMatchedRequestForSearch(index, true);
1589     },
1590
1591     jumpToNextSearchResult: function()
1592     {
1593         if (!this._matchedRequestCount)
1594             return;
1595         var index = this._normalizeSearchResultIndex(this._currentMatchedRequestIndex + 1);
1596         this._highlightNthMatchedRequestForSearch(index, true);
1597     },
1598
1599     searchCanceled: function()
1600     {
1601         delete this._searchRegExp;
1602         this._clearSearchMatchedList();
1603         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, 0);
1604     },
1605
1606     /**
1607      * @param {!WebInspector.NetworkRequest} request
1608      */
1609     revealAndHighlightRequest: function(request)
1610     {
1611         this._removeAllNodeHighlights();
1612
1613         var node = this._nodesByRequestId.get(request.requestId);
1614         if (node) {
1615             node.reveal();
1616             this._highlightNode(node);
1617         }
1618     },
1619
1620     _removeAllNodeHighlights: function()
1621     {
1622         if (this._highlightedNode) {
1623             this._highlightedNode.element().classList.remove("highlighted-row");
1624             delete this._highlightedNode;
1625         }
1626     },
1627
1628     /**
1629      * @param {!WebInspector.NetworkDataGridNode} node
1630      */
1631     _highlightNode: function(node)
1632     {
1633         WebInspector.runCSSAnimationOnce(node.element(), "highlighted-row");
1634         this._highlightedNode = node;
1635     },
1636
1637     /**
1638      * @param {!WebInspector.NetworkRequest} request
1639      * @return {string}
1640      */
1641     _generateCurlCommand: function(request)
1642     {
1643         var command = ["curl"];
1644         // These headers are derived from URL (except "version") and would be added by cURL anyway.
1645         var ignoredHeaders = {"host": 1, "method": 1, "path": 1, "scheme": 1, "version": 1};
1646
1647         function escapeStringWin(str)
1648         {
1649             /* Replace quote by double quote (but not by \") because it is
1650                recognized by both cmd.exe and MS Crt arguments parser.
1651
1652                Replace % by "%" because it could be expanded to an environment
1653                variable value. So %% becomes "%""%". Even if an env variable ""
1654                (2 doublequotes) is declared, the cmd.exe will not
1655                substitute it with its value.
1656
1657                Replace each backslash with double backslash to make sure
1658                MS Crt arguments parser won't collapse them.
1659
1660                Replace new line outside of quotes since cmd.exe doesn't let
1661                to do it inside.
1662             */
1663             return "\"" + str.replace(/"/g, "\"\"")
1664                              .replace(/%/g, "\"%\"")
1665                              .replace(/\\/g, "\\\\")
1666                              .replace(/[\r\n]+/g, "\"^$&\"") + "\"";
1667         }
1668
1669         function escapeStringPosix(str)
1670         {
1671             function escapeCharacter(x)
1672             {
1673                 var code = x.charCodeAt(0);
1674                 if (code < 256) {
1675                     // Add leading zero when needed to not care about the next character.
1676                     return code < 16 ? "\\x0" + code.toString(16) : "\\x" + code.toString(16);
1677                  }
1678                  code = code.toString(16);
1679                  return "\\u" + ("0000" + code).substr(code.length, 4);
1680              }
1681
1682             if (/[^\x20-\x7E]|\'/.test(str)) {
1683                 // Use ANSI-C quoting syntax.
1684                 return "$\'" + str.replace(/\\/g, "\\\\")
1685                                   .replace(/\'/g, "\\\'")
1686                                   .replace(/\n/g, "\\n")
1687                                   .replace(/\r/g, "\\r")
1688                                   .replace(/[^\x20-\x7E]/g, escapeCharacter) + "'";
1689             } else {
1690                 // Use single quote syntax.
1691                 return "'" + str + "'";
1692             }
1693         }
1694
1695         // cURL command expected to run on the same platform that DevTools run
1696         // (it may be different from the inspected page platform).
1697         var escapeString = WebInspector.isWin() ? escapeStringWin : escapeStringPosix;
1698
1699         command.push(escapeString(request.url).replace(/[[{}\]]/g, "\\$&"));
1700
1701         var inferredMethod = "GET";
1702         var data = [];
1703         var requestContentType = request.requestContentType();
1704         if (requestContentType && requestContentType.startsWith("application/x-www-form-urlencoded") && request.requestFormData) {
1705            data.push("--data");
1706            data.push(escapeString(request.requestFormData));
1707            ignoredHeaders["content-length"] = true;
1708            inferredMethod = "POST";
1709         } else if (request.requestFormData) {
1710            data.push("--data-binary");
1711            data.push(escapeString(request.requestFormData));
1712            ignoredHeaders["content-length"] = true;
1713            inferredMethod = "POST";
1714         }
1715
1716         if (request.requestMethod !== inferredMethod) {
1717             command.push("-X");
1718             command.push(request.requestMethod);
1719         }
1720
1721         var requestHeaders = request.requestHeaders();
1722         for (var i = 0; i < requestHeaders.length; i++) {
1723             var header = requestHeaders[i];
1724             var name = header.name.replace(/^:/, ""); // Translate SPDY v3 headers to HTTP headers.
1725             if (name.toLowerCase() in ignoredHeaders)
1726                 continue;
1727             command.push("-H");
1728             command.push(escapeString(name + ": " + header.value));
1729         }
1730         command = command.concat(data);
1731         command.push("--compressed");
1732         return command.join(" ");
1733     },
1734
1735     __proto__: WebInspector.VBox.prototype
1736 }
1737
1738 /** @typedef {function(!WebInspector.NetworkRequest): boolean} */
1739 WebInspector.NetworkLogView.Filter;
1740
1741 /**
1742  * @param {!WebInspector.NetworkLogView.Filter} filter
1743  * @param {!WebInspector.NetworkRequest} request
1744  * @return {boolean}
1745  */
1746 WebInspector.NetworkLogView._negativeFilter = function(filter, request)
1747 {
1748     return !filter(request);
1749 }
1750
1751 /**
1752  * @param {!RegExp} regex
1753  * @param {!WebInspector.NetworkRequest} request
1754  * @return {boolean}
1755  */
1756 WebInspector.NetworkLogView._requestNameOrPathFilter = function(regex, request)
1757 {
1758     return regex.test(request.name()) || regex.test(request.path());
1759 }
1760
1761 /**
1762  * @param {string} value
1763  * @param {!WebInspector.NetworkRequest} request
1764  * @return {boolean}
1765  */
1766 WebInspector.NetworkLogView._requestDomainFilter = function(value, request)
1767 {
1768     return request.domain === value;
1769 }
1770
1771 /**
1772  * @param {!WebInspector.NetworkRequest} request
1773  * @return {boolean}
1774  */
1775 WebInspector.NetworkLogView._runningRequestFilter = function(request)
1776 {
1777     return !request.finished;
1778 }
1779
1780 /**
1781  * @param {string} value
1782  * @param {!WebInspector.NetworkRequest} request
1783  * @return {boolean}
1784  */
1785 WebInspector.NetworkLogView._requestResponseHeaderFilter = function(value, request)
1786 {
1787     return request.responseHeaderValue(value) !== undefined;
1788 }
1789
1790 /**
1791  * @param {string} value
1792  * @param {!WebInspector.NetworkRequest} request
1793  * @return {boolean}
1794  */
1795 WebInspector.NetworkLogView._requestMethodFilter = function(value, request)
1796 {
1797     return request.requestMethod === value;
1798 }
1799
1800 /**
1801  * @param {string} value
1802  * @param {!WebInspector.NetworkRequest} request
1803  * @return {boolean}
1804  */
1805 WebInspector.NetworkLogView._requestMimeTypeFilter = function(value, request)
1806 {
1807     return request.mimeType === value;
1808 }
1809
1810 /**
1811  * @param {string} value
1812  * @param {!WebInspector.NetworkRequest} request
1813  * @return {boolean}
1814  */
1815 WebInspector.NetworkLogView._requestSchemeFilter = function(value, request)
1816 {
1817     return request.scheme === value;
1818 }
1819
1820 /**
1821  * @param {string} value
1822  * @param {!WebInspector.NetworkRequest} request
1823  * @return {boolean}
1824  */
1825 WebInspector.NetworkLogView._requestSetCookieDomainFilter = function(value, request)
1826 {
1827     var cookies = request.responseCookies;
1828     for (var i = 0, l = cookies ? cookies.length : 0; i < l; ++i) {
1829         if (cookies[i].domain() === value)
1830             return false;
1831     }
1832     return false;
1833 }
1834
1835 /**
1836  * @param {string} value
1837  * @param {!WebInspector.NetworkRequest} request
1838  * @return {boolean}
1839  */
1840 WebInspector.NetworkLogView._requestSetCookieNameFilter = function(value, request)
1841 {
1842     var cookies = request.responseCookies;
1843     for (var i = 0, l = cookies ? cookies.length : 0; i < l; ++i) {
1844         if (cookies[i].name() === value)
1845             return false;
1846     }
1847     return false;
1848 }
1849
1850 /**
1851  * @param {string} value
1852  * @param {!WebInspector.NetworkRequest} request
1853  * @return {boolean}
1854  */
1855 WebInspector.NetworkLogView._requestSetCookieValueFilter = function(value, request)
1856 {
1857     var cookies = request.responseCookies;
1858     for (var i = 0, l = cookies ? cookies.length : 0; i < l; ++i) {
1859         if (cookies[i].value() === value)
1860             return false;
1861     }
1862     return false;
1863 }
1864
1865 /**
1866  * @param {string} value
1867  * @param {!WebInspector.NetworkRequest} request
1868  * @return {boolean}
1869  */
1870 WebInspector.NetworkLogView._statusCodeFilter = function(value, request)
1871 {
1872     return ("" + request.statusCode) === value;
1873 }
1874
1875 /**
1876  * @param {!WebInspector.NetworkRequest} request
1877  * @return {boolean}
1878  */
1879 WebInspector.NetworkLogView.HTTPRequestsFilter = function(request)
1880 {
1881     return request.parsedURL.isValid && (request.scheme in WebInspector.NetworkLogView.HTTPSchemas);
1882 }
1883
1884 /**
1885  * @param {!WebInspector.NetworkRequest} request
1886  * @return {boolean}
1887  */
1888 WebInspector.NetworkLogView.NonDevToolsRequestsFilter = function(request)
1889 {
1890     return !WebInspector.NetworkManager.hasDevToolsRequestHeader(request);
1891 }
1892
1893 /**
1894  * @param {!WebInspector.NetworkRequest} request
1895  * @return {boolean}
1896  */
1897 WebInspector.NetworkLogView.FinishedRequestsFilter = function(request)
1898 {
1899     return request.finished;
1900 }
1901
1902 WebInspector.NetworkLogView.EventTypes = {
1903     ViewCleared: "ViewCleared",
1904     RowSizeChanged: "RowSizeChanged",
1905     RequestSelected: "RequestSelected",
1906     SearchCountUpdated: "SearchCountUpdated",
1907     SearchIndexUpdated: "SearchIndexUpdated"
1908 };
1909
1910 /**
1911  * @constructor
1912  * @implements {WebInspector.ContextMenu.Provider}
1913  * @implements {WebInspector.Searchable}
1914  * @extends {WebInspector.Panel}
1915  */
1916 WebInspector.NetworkPanel = function()
1917 {
1918     WebInspector.Panel.call(this, "network");
1919     this.registerRequiredCSS("network/networkPanel.css");
1920
1921     this._panelStatusBarElement = this.element.createChild("div", "panel-status-bar");
1922     this._filterBar = new WebInspector.FilterBar();
1923     this._filtersContainer = this.element.createChild("div", "network-filters-header hidden");
1924     this._filtersContainer.appendChild(this._filterBar.filtersElement());
1925     this._filterBar.addEventListener(WebInspector.FilterBar.Events.FiltersToggled, this._onFiltersToggled, this);
1926     this._filterBar.setName("networkPanel");
1927
1928     this._searchableView = new WebInspector.SearchableView(this);
1929     this._searchableView.show(this.element);
1930     var contentsElement = this._searchableView.element;
1931
1932     this._splitView = new WebInspector.SplitView(true, false, "networkPanelSplitViewState");
1933     this._splitView.show(contentsElement);
1934     this._splitView.hideMain();
1935
1936     var defaultColumnsVisibility = WebInspector.NetworkLogView.defaultColumnsVisibility;
1937     var networkLogColumnsVisibilitySetting = WebInspector.settings.createSetting("networkLogColumnsVisibility", defaultColumnsVisibility);
1938     var savedColumnsVisibility = networkLogColumnsVisibilitySetting.get();
1939     var columnsVisibility = {};
1940     for (var columnId in defaultColumnsVisibility)
1941         columnsVisibility[columnId] = savedColumnsVisibility.hasOwnProperty(columnId) ? savedColumnsVisibility[columnId] : defaultColumnsVisibility[columnId];
1942     networkLogColumnsVisibilitySetting.set(columnsVisibility);
1943
1944     /** @type {!WebInspector.NetworkLogView} */
1945     this._networkLogView = new WebInspector.NetworkLogView(this._filterBar, networkLogColumnsVisibilitySetting);
1946     this._networkLogView.show(this._splitView.sidebarElement());
1947
1948     var viewsContainerView = new WebInspector.VBox();
1949     this._viewsContainerElement = viewsContainerView.element;
1950     this._viewsContainerElement.id = "network-views";
1951     if (!this._networkLogView.usesLargeRows())
1952         this._viewsContainerElement.classList.add("small");
1953     viewsContainerView.show(this._splitView.mainElement());
1954
1955     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ViewCleared, this._onViewCleared, this);
1956     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, this._onRowSizeChanged, this);
1957     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RequestSelected, this._onRequestSelected, this);
1958     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._onSearchCountUpdated, this);
1959     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._onSearchIndexUpdated, this);
1960
1961     this._closeButtonElement = this._viewsContainerElement.createChild("div", "close-button");
1962     this._closeButtonElement.id = "network-close-button";
1963     this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false);
1964     this._viewsContainerElement.appendChild(this._closeButtonElement);
1965
1966     var statusBarItems = this._networkLogView.statusBarItems();
1967     for (var i = 0; i < statusBarItems.length; ++i)
1968         this._panelStatusBarElement.appendChild(statusBarItems[i]);
1969
1970     /**
1971      * @this {WebInspector.NetworkPanel}
1972      * @return {?WebInspector.SourceFrame}
1973      */
1974     function sourceFrameGetter()
1975     {
1976         return this._networkItemView.currentSourceFrame();
1977     }
1978     WebInspector.GoToLineDialog.install(this, sourceFrameGetter.bind(this));
1979 }
1980
1981 WebInspector.NetworkPanel.prototype = {
1982     /**
1983      * @param {!WebInspector.Event} event
1984      */
1985     _onFiltersToggled: function(event)
1986     {
1987         var toggled = /** @type {boolean} */ (event.data);
1988         this._filtersContainer.classList.toggle("hidden", !toggled);
1989         this.element.classList.toggle("filters-toggled", toggled);
1990         this.doResize();
1991     },
1992
1993     /**
1994      * @return {!Array.<!Element>}
1995      */
1996     elementsToRestoreScrollPositionsFor: function()
1997     {
1998         return this._networkLogView.elementsToRestoreScrollPositionsFor();
1999     },
2000
2001     /**
2002      * @return {!WebInspector.SearchableView}
2003      */
2004     searchableView: function()
2005     {
2006         return this._searchableView;
2007     },
2008
2009     /**
2010      * @param {!KeyboardEvent} event
2011      */
2012     handleShortcut: function(event)
2013     {
2014         if (this._viewingRequestMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
2015             this._toggleGridMode();
2016             event.handled = true;
2017             return;
2018         }
2019
2020         WebInspector.Panel.prototype.handleShortcut.call(this, event);
2021     },
2022
2023     wasShown: function()
2024     {
2025         WebInspector.Panel.prototype.wasShown.call(this);
2026     },
2027
2028     /**
2029      * @param {!WebInspector.NetworkRequest} request
2030      */
2031     revealAndHighlightRequest: function(request)
2032     {
2033         this._toggleGridMode();
2034         if (request)
2035             this._networkLogView.revealAndHighlightRequest(request);
2036     },
2037
2038     /**
2039      * @param {!WebInspector.Event} event
2040      */
2041     _onViewCleared: function(event)
2042     {
2043         this._closeVisibleRequest();
2044         this._toggleGridMode();
2045         this._viewsContainerElement.removeChildren();
2046         this._viewsContainerElement.appendChild(this._closeButtonElement);
2047     },
2048
2049     /**
2050      * @param {!WebInspector.Event} event
2051      */
2052     _onRowSizeChanged: function(event)
2053     {
2054         this._viewsContainerElement.classList.toggle("small", !event.data.largeRows);
2055     },
2056
2057     /**
2058      * @param {!WebInspector.Event} event
2059      */
2060     _onSearchCountUpdated: function(event)
2061     {
2062         var count = /** @type {number} */ (event.data);
2063         this._searchableView.updateSearchMatchesCount(count);
2064     },
2065
2066     /**
2067      * @param {!WebInspector.Event} event
2068      */
2069     _onSearchIndexUpdated: function(event)
2070     {
2071         var index = /** @type {number} */ (event.data);
2072         this._searchableView.updateCurrentMatchIndex(index);
2073     },
2074
2075     /**
2076      * @param {!WebInspector.Event} event
2077      */
2078     _onRequestSelected: function(event)
2079     {
2080         var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
2081         this._showRequest(request);
2082     },
2083
2084     /**
2085      * @param {?WebInspector.NetworkRequest} request
2086      */
2087     _showRequest: function(request)
2088     {
2089         if (!request)
2090             return;
2091
2092         this._toggleViewingRequestMode();
2093
2094         if (this._networkItemView) {
2095             this._networkItemView.detach();
2096             delete this._networkItemView;
2097         }
2098
2099         var view = new WebInspector.NetworkItemView(request);
2100         view.show(this._viewsContainerElement);
2101         this._networkItemView = view;
2102     },
2103
2104     _closeVisibleRequest: function()
2105     {
2106         this.element.classList.remove("viewing-resource");
2107
2108         if (this._networkItemView) {
2109             this._networkItemView.detach();
2110             delete this._networkItemView;
2111         }
2112     },
2113
2114     _toggleGridMode: function()
2115     {
2116         if (this._viewingRequestMode) {
2117             this._viewingRequestMode = false;
2118             this.element.classList.remove("viewing-resource");
2119             this._splitView.hideMain();
2120         }
2121
2122         this._networkLogView.switchViewMode(true);
2123         this._networkLogView.setAllowPopover(true);
2124         this._networkLogView._allowRequestSelection = false;
2125     },
2126
2127     _toggleViewingRequestMode: function()
2128     {
2129         if (this._viewingRequestMode)
2130             return;
2131         this._viewingRequestMode = true;
2132
2133         this.element.classList.add("viewing-resource");
2134         this._splitView.showBoth();
2135         this._networkLogView.setAllowPopover(false);
2136         this._networkLogView._allowRequestSelection = true;
2137         this._networkLogView.switchViewMode(false);
2138     },
2139
2140     /**
2141      * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
2142      * @param {boolean} shouldJump
2143      * @param {boolean=} jumpBackwards
2144      */
2145     performSearch: function(searchConfig, shouldJump, jumpBackwards)
2146     {
2147         this._networkLogView.performSearch(searchConfig, shouldJump, jumpBackwards);
2148     },
2149
2150     jumpToPreviousSearchResult: function()
2151     {
2152         this._networkLogView.jumpToPreviousSearchResult();
2153     },
2154
2155     /**
2156      * @return {boolean}
2157      */
2158     supportsCaseSensitiveSearch: function()
2159     {
2160         return false;
2161     },
2162
2163     /**
2164      * @return {boolean}
2165      */
2166     supportsRegexSearch: function()
2167     {
2168         return false;
2169     },
2170
2171     jumpToNextSearchResult: function()
2172     {
2173         this._networkLogView.jumpToNextSearchResult();
2174     },
2175
2176     searchCanceled: function()
2177     {
2178         this._networkLogView.searchCanceled();
2179     },
2180
2181     /**
2182      * @param {!Event} event
2183      * @param {!WebInspector.ContextMenu} contextMenu
2184      * @param {!Object} target
2185      * @this {WebInspector.NetworkPanel}
2186      */
2187     appendApplicableItems: function(event, contextMenu, target)
2188     {
2189         /**
2190          * @this {WebInspector.NetworkPanel}
2191          */
2192         function reveal(request)
2193         {
2194             WebInspector.inspectorView.setCurrentPanel(this);
2195             this.revealAndHighlightRequest(request);
2196         }
2197
2198         /**
2199          * @this {WebInspector.NetworkPanel}
2200          */
2201         function appendRevealItem(request)
2202         {
2203             var revealText = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Network panel" : "Reveal in Network Panel");
2204             contextMenu.appendItem(revealText, reveal.bind(this, request));
2205         }
2206
2207         if (target instanceof WebInspector.Resource) {
2208             var resource = /** @type {!WebInspector.Resource} */ (target);
2209             if (resource.request)
2210                 appendRevealItem.call(this, resource.request);
2211             return;
2212         }
2213         if (target instanceof WebInspector.UISourceCode) {
2214             var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (target);
2215             var resource = WebInspector.resourceForURL(uiSourceCode.url);
2216             if (resource && resource.request)
2217                 appendRevealItem.call(this, resource.request);
2218             return;
2219         }
2220
2221         if (!(target instanceof WebInspector.NetworkRequest))
2222             return;
2223         var request = /** @type {!WebInspector.NetworkRequest} */ (target);
2224         if (this._networkItemView && this._networkItemView.isShowing() && this._networkItemView.request() === request)
2225             return;
2226
2227         appendRevealItem.call(this, request);
2228     },
2229
2230     __proto__: WebInspector.Panel.prototype
2231 }
2232
2233 /**
2234  * @constructor
2235  * @implements {WebInspector.ContextMenu.Provider}
2236  */
2237 WebInspector.NetworkPanel.ContextMenuProvider = function()
2238 {
2239 }
2240
2241 WebInspector.NetworkPanel.ContextMenuProvider.prototype = {
2242     /**
2243      * @param {!Event} event
2244      * @param {!WebInspector.ContextMenu} contextMenu
2245      * @param {!Object} target
2246      */
2247     appendApplicableItems: function(event, contextMenu, target)
2248     {
2249         WebInspector.NetworkPanel._instance().appendApplicableItems(event, contextMenu, target);
2250     }
2251 }
2252
2253 /**
2254  * @constructor
2255  * @implements {WebInspector.Revealer}
2256  */
2257 WebInspector.NetworkPanel.RequestRevealer = function()
2258 {
2259 }
2260
2261 WebInspector.NetworkPanel.RequestRevealer.prototype = {
2262     /**
2263      * @param {!Object} request
2264      * @param {number=} lineNumber
2265      * @return {!Promise}
2266      */
2267     reveal: function(request, lineNumber)
2268     {
2269         if (request instanceof WebInspector.NetworkRequest) {
2270
2271             var panel = WebInspector.NetworkPanel._instance();
2272             WebInspector.inspectorView.setCurrentPanel(panel);
2273             panel.revealAndHighlightRequest(request);
2274             return Promise.resolve();
2275         }
2276         return Promise.rejectWithError("Internal error: not a network request");
2277     }
2278 }
2279
2280 /**
2281  * @constructor
2282  * @implements {WebInspector.TimelineGrid.Calculator}
2283  */
2284 WebInspector.NetworkTimeCalculator = function(startAtZero)
2285 {
2286     this.startAtZero = startAtZero;
2287 }
2288
2289 /** @type {!WebInspector.UIStringFormat} */
2290 WebInspector.NetworkTimeCalculator._latencyDownloadTotalFormat = new WebInspector.UIStringFormat("%s latency, %s download (%s total)");
2291
2292 /** @type {!WebInspector.UIStringFormat} */
2293 WebInspector.NetworkTimeCalculator._latencyFormat = new WebInspector.UIStringFormat("%s latency");
2294
2295 /** @type {!WebInspector.UIStringFormat} */
2296 WebInspector.NetworkTimeCalculator._downloadFormat = new WebInspector.UIStringFormat("%s download");
2297
2298 /** @type {!WebInspector.UIStringFormat} */
2299 WebInspector.NetworkTimeCalculator._fromServiceWorkerFormat = new WebInspector.UIStringFormat("%s (from ServiceWorker)");
2300
2301 /** @type {!WebInspector.UIStringFormat} */
2302 WebInspector.NetworkTimeCalculator._fromCacheFormat = new WebInspector.UIStringFormat("%s (from cache)");
2303
2304 WebInspector.NetworkTimeCalculator.prototype = {
2305     /**
2306      * @override
2307      * @return {number}
2308      */
2309     paddingLeft: function()
2310     {
2311         return 0;
2312     },
2313
2314     /**
2315      * @override
2316      * @param {number} time
2317      * @return {number}
2318      */
2319     computePosition: function(time)
2320     {
2321         return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea;
2322     },
2323
2324     /**
2325      * @override
2326      * @param {number} value
2327      * @param {number=} precision
2328      * @return {string}
2329      */
2330     formatTime: function(value, precision)
2331     {
2332         return Number.secondsToString(value);
2333     },
2334
2335     /**
2336      * @override
2337      * @return {number}
2338      */
2339     minimumBoundary: function()
2340     {
2341         return this._minimumBoundary;
2342     },
2343
2344     /**
2345      * @override
2346      * @return {number}
2347      */
2348     zeroTime: function()
2349     {
2350         return this._minimumBoundary;
2351     },
2352
2353     /**
2354      * @override
2355      * @return {number}
2356      */
2357     maximumBoundary: function()
2358     {
2359         return this._maximumBoundary;
2360     },
2361
2362     /**
2363      * @override
2364      * @return {number}
2365      */
2366     boundarySpan: function()
2367     {
2368         return this._maximumBoundary - this._minimumBoundary;
2369     },
2370
2371     reset: function()
2372     {
2373         delete this._minimumBoundary;
2374         delete this._maximumBoundary;
2375     },
2376
2377     /**
2378      * @return {number}
2379      */
2380     _value: function(item)
2381     {
2382         return 0;
2383     },
2384
2385     /**
2386      * @param {number} clientWidth
2387      */
2388     setDisplayWindow: function(clientWidth)
2389     {
2390         this._workingArea = clientWidth;
2391     },
2392
2393     /**
2394      * @param {!WebInspector.NetworkRequest} request
2395      * @return {!{start: number, middle: number, end: number}}
2396      */
2397     computeBarGraphPercentages: function(request)
2398     {
2399         if (request.startTime !== -1)
2400             var start = ((request.startTime - this._minimumBoundary) / this.boundarySpan()) * 100;
2401         else
2402             var start = 0;
2403
2404         if (request.responseReceivedTime !== -1)
2405             var middle = ((request.responseReceivedTime - this._minimumBoundary) / this.boundarySpan()) * 100;
2406         else
2407             var middle = (this.startAtZero ? start : 100);
2408
2409         if (request.endTime !== -1)
2410             var end = ((request.endTime - this._minimumBoundary) / this.boundarySpan()) * 100;
2411         else
2412             var end = (this.startAtZero ? middle : 100);
2413
2414         if (this.startAtZero) {
2415             end -= start;
2416             middle -= start;
2417             start = 0;
2418         }
2419
2420         return {start: start, middle: middle, end: end};
2421     },
2422
2423     /**
2424      * @param {number} eventTime
2425      * @return {number}
2426      */
2427     computePercentageFromEventTime: function(eventTime)
2428     {
2429         // This function computes a percentage in terms of the total loading time
2430         // of a specific event. If startAtZero is set, then this is useless, and we
2431         // want to return 0.
2432         if (eventTime !== -1 && !this.startAtZero)
2433             return ((eventTime - this._minimumBoundary) / this.boundarySpan()) * 100;
2434
2435         return 0;
2436     },
2437
2438     /**
2439      * @param {number} eventTime
2440      * @return {boolean}
2441      */
2442     updateBoundariesForEventTime: function(eventTime)
2443     {
2444         if (eventTime === -1 || this.startAtZero)
2445             return false;
2446
2447         if (typeof this._maximumBoundary === "undefined" || eventTime > this._maximumBoundary) {
2448             this._maximumBoundary = eventTime;
2449             return true;
2450         }
2451         return false;
2452     },
2453
2454     /**
2455      * @param {!WebInspector.NetworkRequest} request
2456      * @return {!{left: string, right: string, tooltip: (string|undefined)}}
2457      */
2458     computeBarGraphLabels: function(request)
2459     {
2460         var rightLabel = "";
2461         if (request.responseReceivedTime !== -1 && request.endTime !== -1)
2462             rightLabel = Number.secondsToString(request.endTime - request.responseReceivedTime);
2463
2464         var hasLatency = request.latency > 0;
2465         if (hasLatency)
2466             var leftLabel = Number.secondsToString(request.latency);
2467         else
2468             var leftLabel = rightLabel;
2469
2470         if (request.timing)
2471             return {left: leftLabel, right: rightLabel};
2472
2473         if (hasLatency && rightLabel) {
2474             var total = Number.secondsToString(request.duration);
2475             var tooltip = WebInspector.NetworkTimeCalculator._latencyDownloadTotalFormat.format(leftLabel, rightLabel, total);
2476         } else if (hasLatency)
2477             var tooltip = WebInspector.NetworkTimeCalculator._latencyFormat.format(leftLabel);
2478         else if (rightLabel)
2479             var tooltip = WebInspector.NetworkTimeCalculator._downloadFormat.format(rightLabel);
2480
2481         if (request.fetchedViaServiceWorker)
2482             tooltip = WebInspector.NetworkTimeCalculator._fromServiceWorkerFormat.format(tooltip);
2483         else if (request.cached())
2484             tooltip = WebInspector.NetworkTimeCalculator._fromCacheFormat.format(tooltip);
2485         return {left: leftLabel, right: rightLabel, tooltip: tooltip};
2486     },
2487
2488     /**
2489      * @param {!WebInspector.NetworkRequest} request
2490      * @return {boolean}
2491      */
2492     updateBoundaries: function(request)
2493     {
2494         var didChange = false;
2495
2496         var lowerBound;
2497         if (this.startAtZero)
2498             lowerBound = 0;
2499         else
2500             lowerBound = this._lowerBound(request);
2501
2502         if (lowerBound !== -1 && (typeof this._minimumBoundary === "undefined" || lowerBound < this._minimumBoundary)) {
2503             this._minimumBoundary = lowerBound;
2504             didChange = true;
2505         }
2506
2507         var upperBound = this._upperBound(request);
2508         if (upperBound !== -1 && (typeof this._maximumBoundary === "undefined" || upperBound > this._maximumBoundary)) {
2509             this._maximumBoundary = upperBound;
2510             didChange = true;
2511         }
2512
2513         return didChange;
2514     },
2515
2516     /**
2517      * @param {!WebInspector.NetworkRequest} request
2518      * @return {number}
2519      */
2520     _lowerBound: function(request)
2521     {
2522         return 0;
2523     },
2524
2525     /**
2526      * @param {!WebInspector.NetworkRequest} request
2527      * @return {number}
2528      */
2529     _upperBound: function(request)
2530     {
2531         return 0;
2532     }
2533 }
2534
2535 /**
2536  * @constructor
2537  * @extends {WebInspector.NetworkTimeCalculator}
2538  */
2539 WebInspector.NetworkTransferTimeCalculator = function()
2540 {
2541     WebInspector.NetworkTimeCalculator.call(this, false);
2542 }
2543
2544 WebInspector.NetworkTransferTimeCalculator.prototype = {
2545     /**
2546      * @override
2547      * @param {number} value
2548      * @param {number=} precision
2549      * @return {string}
2550      */
2551     formatTime: function(value, precision)
2552     {
2553         return Number.secondsToString(value - this.zeroTime());
2554     },
2555
2556     /**
2557      * @override
2558      * @param {!WebInspector.NetworkRequest} request
2559      * @return {number}
2560      */
2561     _lowerBound: function(request)
2562     {
2563         return request.startTime;
2564     },
2565
2566     /**
2567      * @override
2568      * @param {!WebInspector.NetworkRequest} request
2569      * @return {number}
2570      */
2571     _upperBound: function(request)
2572     {
2573         return request.endTime;
2574     },
2575
2576     __proto__: WebInspector.NetworkTimeCalculator.prototype
2577 }
2578
2579 /**
2580  * @constructor
2581  * @extends {WebInspector.NetworkTimeCalculator}
2582  */
2583 WebInspector.NetworkTransferDurationCalculator = function()
2584 {
2585     WebInspector.NetworkTimeCalculator.call(this, true);
2586 }
2587
2588 WebInspector.NetworkTransferDurationCalculator.prototype = {
2589     /**
2590      * @override
2591      * @param {number} value
2592      * @param {number=} precision
2593      * @return {string}
2594      */
2595     formatTime: function(value, precision)
2596     {
2597         return Number.secondsToString(value);
2598     },
2599
2600     /**
2601      * @override
2602      * @param {!WebInspector.NetworkRequest} request
2603      * @return {number}
2604      */
2605     _upperBound: function(request)
2606     {
2607         return request.duration;
2608     },
2609
2610     __proto__: WebInspector.NetworkTimeCalculator.prototype
2611 }
2612
2613 /**
2614  * @constructor
2615  * @extends {WebInspector.SortableDataGridNode}
2616  * @param {!WebInspector.NetworkLogView} parentView
2617  * @param {!WebInspector.NetworkRequest} request
2618  */
2619 WebInspector.NetworkDataGridNode = function(parentView, request)
2620 {
2621     WebInspector.SortableDataGridNode.call(this, {});
2622     this._parentView = parentView;
2623     this._request = request;
2624     this._linkifier = new WebInspector.Linkifier();
2625     this._isFilteredOut = true;
2626     this._isMatchingSearchQuery = false;
2627     this._staleGraph = true;
2628 }
2629
2630 WebInspector.NetworkDataGridNode._hoveredRowSymbol = Symbol("hoveredRow");
2631
2632 WebInspector.NetworkDataGridNode.prototype = {
2633     /**
2634      * @return {!WebInspector.NetworkRequest}
2635      */
2636     request: function()
2637     {
2638         return this._request;
2639     },
2640
2641     /**
2642      * @override
2643      * @return {number}
2644      */
2645     nodeSelfHeight: function()
2646     {
2647         return this._parentView.rowHeight();
2648     },
2649
2650     /** override */
2651     createCells: function()
2652     {
2653         this._nameCell = null;
2654         this._timelineCell = null;
2655         this._initiatorCell = null;
2656
2657         this._element.classList.toggle("network-error-row", this._isFailed());
2658         WebInspector.SortableDataGridNode.prototype.createCells.call(this);
2659
2660         this._updateGraph();
2661     },
2662
2663     /**
2664      * @override
2665      * @param {string} columnIdentifier
2666      * @return {!Element}
2667      */
2668     createCell: function(columnIdentifier)
2669     {
2670         var cell = this.createTD(columnIdentifier);
2671         switch (columnIdentifier) {
2672         case "name": this._renderNameCell(cell); break;
2673         case "timeline": this._createTimelineBar(cell); break;
2674         case "method": cell.setTextAndTitle(this._request.requestMethod); break;
2675         case "status": this._renderStatusCell(cell); break;
2676         case "scheme": cell.setTextAndTitle(this._request.scheme); break;
2677         case "domain": cell.setTextAndTitle(this._request.domain); break;
2678         case "remoteAddress": cell.setTextAndTitle(this._request.remoteAddress()); break;
2679         case "cookies": cell.setTextAndTitle(this._arrayLength(this._request.requestCookies)); break;
2680         case "setCookies": cell.setTextAndTitle(this._arrayLength(this._request.responseCookies)); break;
2681         case "connectionId": cell.setTextAndTitle(this._request.connectionId); break;
2682         case "type": cell.setTextAndTitle(this._request.mimeType || this._request.requestContentType() || ""); break;
2683         case "initiator": this._renderInitiatorCell(cell); break;
2684         case "size": this._renderSizeCell(cell); break;
2685         case "time": this._renderTimeCell(cell); break;
2686         default: cell.setTextAndTitle(this._request.responseHeaderValue(columnIdentifier) || ""); break;
2687         }
2688
2689         return cell;
2690     },
2691
2692     /**
2693      * @param {?Array} array
2694      * @return {string}
2695      */
2696     _arrayLength: function(array)
2697     {
2698         return array ? "" + array.length : "";
2699     },
2700
2701     /**
2702      * @override
2703      * @protected
2704      */
2705     willAttach: function()
2706     {
2707         if (this._staleGraph)
2708             this._updateGraph();
2709         if (this._initiatorCell && this._request.initiatorInfo().type === WebInspector.NetworkRequest.InitiatorType.Script)
2710             this._initiatorCell.insertBefore(this._linkifiedInitiatorAnchor, this._initiatorCell.firstChild);
2711     },
2712
2713     wasDetached: function()
2714     {
2715         if (this._linkifiedInitiatorAnchor)
2716             this._linkifiedInitiatorAnchor.remove();
2717     },
2718
2719     dispose: function()
2720     {
2721         this._linkifier.reset();
2722     },
2723
2724     _onClick: function()
2725     {
2726         if (!this._parentView.allowRequestSelection())
2727             this.select();
2728     },
2729
2730     select: function()
2731     {
2732         this._parentView.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RequestSelected, this._request);
2733         WebInspector.SortableDataGridNode.prototype.select.apply(this, arguments);
2734
2735         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
2736             action: WebInspector.UserMetrics.UserActionNames.NetworkRequestSelected,
2737             url: this._request.url
2738         });
2739     },
2740
2741     /**
2742      * @param {!RegExp=} regexp
2743      * @return {!Array.<!Object>}
2744      */
2745     highlightMatchedSubstring: function(regexp)
2746     {
2747         // Ensure element is created.
2748         this.element();
2749         var domChanges = [];
2750         var matchInfo = this._nameCell.textContent.match(regexp);
2751         if (matchInfo)
2752             WebInspector.highlightSearchResult(this._nameCell, matchInfo.index, matchInfo[0].length, domChanges);
2753         return domChanges;
2754     },
2755
2756     _openInNewTab: function()
2757     {
2758         InspectorFrontendHost.openInNewTab(this._request.url);
2759     },
2760
2761     get selectable()
2762     {
2763         return this._parentView.allowRequestSelection();
2764     },
2765
2766     /**
2767      * @param {!Element} cell
2768      */
2769     _createTimelineBar: function(cell)
2770     {
2771         cell = cell.createChild("div");
2772         this._timelineCell = cell;
2773
2774         cell.className = "network-graph-side";
2775
2776         this._barAreaElement = cell.createChild("div", "network-graph-bar-area");
2777         this._barAreaElement.request = this._request;
2778
2779         var type = this._request.resourceType().name();
2780         var cached = this._request.cached();
2781
2782         this._barLeftElement = this._barAreaElement.createChild("div", "network-graph-bar");
2783         this._barLeftElement.classList.add(type, "waiting");
2784         this._barLeftElement.classList.toggle("cached", cached);
2785
2786         this._barRightElement = this._barAreaElement.createChild("div", "network-graph-bar");
2787         this._barRightElement.classList.add(type);
2788         this._barRightElement.classList.toggle("cached", cached);
2789
2790         this._labelLeftElement = this._barAreaElement.createChild("div", "network-graph-label");
2791         this._labelLeftElement.classList.add("waiting");
2792
2793         this._labelRightElement = this._barAreaElement.createChild("div", "network-graph-label");
2794
2795         cell.addEventListener("mouseover", this._onMouseOver.bind(this), false);
2796     },
2797
2798     /**
2799      * @param {!Event} event
2800      */
2801     _onMouseOver: function(event)
2802     {
2803         this._refreshLabelPositions();
2804         this._parentView[WebInspector.NetworkDataGridNode._hoveredRowSymbol] = this;
2805     },
2806
2807     /**
2808      * @return {boolean}
2809      */
2810     _isFailed: function()
2811     {
2812         return (this._request.failed && !this._request.statusCode) || (this._request.statusCode >= 400);
2813     },
2814
2815     /**
2816      * @param {!Element} cell
2817      */
2818     _renderNameCell: function(cell)
2819     {
2820         this._nameCell = cell;
2821         cell.addEventListener("click", this._onClick.bind(this), false);
2822         cell.addEventListener("dblclick", this._openInNewTab.bind(this), false);
2823         var iconElement;
2824         if (this._request.resourceType() === WebInspector.resourceTypes.Image) {
2825             var previewImage = createElementWithClass("img", "image-network-icon-preview");
2826             this._request.populateImageSource(previewImage);
2827
2828             iconElement = createElementWithClass("div", "icon");
2829             iconElement.appendChild(previewImage);
2830         } else {
2831             iconElement = createElementWithClass("img", "icon");
2832         }
2833         iconElement.classList.add(this._request.resourceType().name());
2834
2835         cell.appendChild(iconElement);
2836         cell.createTextChild(this._request.name());
2837         this._appendSubtitle(cell, this._request.path());
2838         cell.title = this._request.url;
2839     },
2840
2841     /**
2842      * @param {!Element} cell
2843      */
2844     _renderStatusCell: function(cell)
2845     {
2846         cell.classList.toggle("network-dim-cell", !this._isFailed() && (this._request.cached() || !this._request.statusCode));
2847
2848         if (this._request.failed && !this._request.canceled) {
2849             var failText = WebInspector.UIString("(failed)");
2850             if (this._request.localizedFailDescription) {
2851                 cell.createTextChild(failText);
2852                 this._appendSubtitle(cell, this._request.localizedFailDescription);
2853                 cell.title = failText + " " + this._request.localizedFailDescription;
2854             } else
2855                 cell.setTextAndTitle(failText);
2856         } else if (this._request.statusCode) {
2857             cell.createTextChild("" + this._request.statusCode);
2858             this._appendSubtitle(cell, this._request.statusText);
2859             cell.title = this._request.statusCode + " " + this._request.statusText;
2860         } else if (this._request.parsedURL.isDataURL()) {
2861             cell.setTextAndTitle(WebInspector.UIString("(data)"));
2862         } else if (this._request.canceled) {
2863             cell.setTextAndTitle(WebInspector.UIString("(canceled)"));
2864         } else if (this._request.finished) {
2865             cell.setTextAndTitle(WebInspector.UIString("Finished"));
2866         } else {
2867             cell.setTextAndTitle(WebInspector.UIString("(pending)"));
2868         }
2869     },
2870
2871     /**
2872      * @param {!Element} cell
2873      */
2874     _renderInitiatorCell: function(cell)
2875     {
2876         this._initiatorCell = cell;
2877         var request = this._request;
2878         var initiator = request.initiatorInfo();
2879
2880         switch (initiator.type) {
2881         case WebInspector.NetworkRequest.InitiatorType.Parser:
2882             cell.title = initiator.url + ":" + initiator.lineNumber;
2883             cell.appendChild(WebInspector.linkifyResourceAsNode(initiator.url, initiator.lineNumber - 1));
2884             this._appendSubtitle(cell, WebInspector.UIString("Parser"));
2885             break;
2886
2887         case WebInspector.NetworkRequest.InitiatorType.Redirect:
2888             cell.title = initiator.url;
2889             console.assert(request.redirectSource);
2890             var redirectSource = /** @type {!WebInspector.NetworkRequest} */ (request.redirectSource);
2891             cell.appendChild(WebInspector.linkifyRequestAsNode(redirectSource));
2892             this._appendSubtitle(cell, WebInspector.UIString("Redirect"));
2893             break;
2894
2895         case WebInspector.NetworkRequest.InitiatorType.Script:
2896             if (!this._linkifiedInitiatorAnchor) {
2897                 this._linkifiedInitiatorAnchor = this._linkifier.linkifyScriptLocation(request.target(), null, initiator.url, initiator.lineNumber - 1, initiator.columnNumber - 1);
2898                 this._linkifiedInitiatorAnchor.title = "";
2899             }
2900             cell.appendChild(this._linkifiedInitiatorAnchor);
2901             this._appendSubtitle(cell, WebInspector.UIString("Script"));
2902             cell.classList.add("network-script-initiated");
2903             cell.request = request;
2904             break;
2905
2906         default:
2907             cell.title = "";
2908             cell.classList.add("network-dim-cell");
2909             cell.setTextAndTitle(WebInspector.UIString("Other"));
2910         }
2911     },
2912
2913     /**
2914      * @param {!Element} cell
2915      */
2916     _renderSizeCell: function(cell)
2917     {
2918         if (this._request.fetchedViaServiceWorker) {
2919             cell.setTextAndTitle(WebInspector.UIString("(from ServiceWorker)"));
2920             cell.classList.add("network-dim-cell");
2921         } else if (this._request.cached()) {
2922             cell.setTextAndTitle(WebInspector.UIString("(from cache)"));
2923             cell.classList.add("network-dim-cell");
2924         } else {
2925             var resourceSize = Number.bytesToString(this._request.resourceSize);
2926             var transferSize = Number.bytesToString(this._request.transferSize);
2927             cell.setTextAndTitle(transferSize);
2928             this._appendSubtitle(cell, resourceSize);
2929         }
2930     },
2931
2932     /**
2933      * @param {!Element} cell
2934      */
2935     _renderTimeCell: function(cell)
2936     {
2937         if (this._request.duration > 0) {
2938             cell.setTextAndTitle(Number.secondsToString(this._request.duration));
2939             this._appendSubtitle(cell, Number.secondsToString(this._request.latency));
2940         } else {
2941             cell.classList.add("network-dim-cell");
2942             cell.setTextAndTitle(WebInspector.UIString("Pending"));
2943         }
2944     },
2945
2946     /**
2947      * @param {!Element} cellElement
2948      * @param {string} subtitleText
2949      */
2950     _appendSubtitle: function(cellElement, subtitleText)
2951     {
2952         var subtitleElement = createElement("div");
2953         subtitleElement.className = "network-cell-subtitle";
2954         subtitleElement.textContent = subtitleText;
2955         cellElement.appendChild(subtitleElement);
2956     },
2957
2958     refreshGraph: function()
2959     {
2960         if (!this._timelineCell)
2961             return;
2962         this._staleGraph = true;
2963         if (this.attached())
2964             this.dataGrid.scheduleUpdate();
2965     },
2966
2967     _updateGraph: function()
2968     {
2969         this._staleGraph = false;
2970         if (!this._timelineCell)
2971             return;
2972
2973         var calculator = this._parentView.calculator();
2974         var percentages = calculator.computeBarGraphPercentages(this._request);
2975         this._percentages = percentages;
2976
2977         this._barAreaElement.classList.remove("hidden");
2978
2979         this._barLeftElement.style.setProperty("left", percentages.start + "%");
2980         this._barLeftElement.style.setProperty("right", (100 - percentages.middle) + "%");
2981
2982         this._barRightElement.style.setProperty("left", percentages.middle + "%");
2983         this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
2984
2985         var labels = calculator.computeBarGraphLabels(this._request);
2986         this._labelLeftElement.textContent = labels.left;
2987         this._labelRightElement.textContent = labels.right;
2988
2989         var tooltip = (labels.tooltip || "");
2990         this._barLeftElement.title = tooltip;
2991         this._labelLeftElement.title = tooltip;
2992         this._labelRightElement.title = tooltip;
2993         this._barRightElement.title = tooltip;
2994
2995         if (this._parentView[WebInspector.NetworkDataGridNode._hoveredRowSymbol] === this)
2996             this._refreshLabelPositions();
2997     },
2998
2999     _refreshLabelPositions: function()
3000     {
3001         if (!this._percentages)
3002             return;
3003         this._labelLeftElement.style.removeProperty("left");
3004         this._labelLeftElement.style.removeProperty("right");
3005         this._labelLeftElement.classList.remove("before");
3006         this._labelLeftElement.classList.remove("hidden");
3007
3008         this._labelRightElement.style.removeProperty("left");
3009         this._labelRightElement.style.removeProperty("right");
3010         this._labelRightElement.classList.remove("after");
3011         this._labelRightElement.classList.remove("hidden");
3012
3013         const labelPadding = 10;
3014         const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
3015         const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;
3016
3017         if (this._barLeftElement) {
3018             var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
3019             var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
3020         } else {
3021             var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
3022             var rightBarWidth = barRightElementOffsetWidth - labelPadding;
3023         }
3024
3025         const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
3026         const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;
3027
3028         const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
3029         const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
3030         const graphElementOffsetWidth = this._timelineCell.offsetWidth;
3031
3032         if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
3033             var leftHidden = true;
3034
3035         if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
3036             var rightHidden = true;
3037
3038         if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
3039             // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
3040             if (labelBefore && !labelAfter)
3041                 leftHidden = true;
3042             else if (labelAfter && !labelBefore)
3043                 rightHidden = true;
3044         }
3045
3046         if (labelBefore) {
3047             if (leftHidden)
3048                 this._labelLeftElement.classList.add("hidden");
3049             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
3050             this._labelLeftElement.classList.add("before");
3051         } else {
3052             this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
3053             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
3054         }
3055
3056         if (labelAfter) {
3057             if (rightHidden)
3058                 this._labelRightElement.classList.add("hidden");
3059             this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
3060             this._labelRightElement.classList.add("after");
3061         } else {
3062             this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
3063             this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
3064         }
3065     },
3066
3067     __proto__: WebInspector.SortableDataGridNode.prototype
3068 }
3069
3070 /**
3071  * @param {!WebInspector.NetworkDataGridNode} a
3072  * @param {!WebInspector.NetworkDataGridNode} b
3073  * @return {number}
3074  */
3075 WebInspector.NetworkDataGridNode.NameComparator = function(a, b)
3076 {
3077     var aFileName = a._request.name();
3078     var bFileName = b._request.name();
3079     if (aFileName > bFileName)
3080         return 1;
3081     if (bFileName > aFileName)
3082         return -1;
3083     return a._request.indentityCompare(b._request);
3084 }
3085
3086 /**
3087  * @param {!WebInspector.NetworkDataGridNode} a
3088  * @param {!WebInspector.NetworkDataGridNode} b
3089  * @return {number}
3090  */
3091 WebInspector.NetworkDataGridNode.RemoteAddressComparator = function(a, b)
3092 {
3093     var aRemoteAddress = a._request.remoteAddress();
3094     var bRemoteAddress = b._request.remoteAddress();
3095     if (aRemoteAddress > bRemoteAddress)
3096         return 1;
3097     if (bRemoteAddress > aRemoteAddress)
3098         return -1;
3099     return a._request.indentityCompare(b._request);
3100 }
3101
3102 /**
3103  * @param {!WebInspector.NetworkDataGridNode} a
3104  * @param {!WebInspector.NetworkDataGridNode} b
3105  * @return {number}
3106  */
3107 WebInspector.NetworkDataGridNode.SizeComparator = function(a, b)
3108 {
3109     if (b._request.cached() && !a._request.cached())
3110         return 1;
3111     if (a._request.cached() && !b._request.cached())
3112         return -1;
3113     return (a._request.transferSize - b._request.transferSize) || a._request.indentityCompare(b._request);
3114 }
3115
3116 /**
3117  * @param {!WebInspector.NetworkDataGridNode} a
3118  * @param {!WebInspector.NetworkDataGridNode} b
3119  * @return {number}
3120  */
3121 WebInspector.NetworkDataGridNode.InitiatorComparator = function(a, b)
3122 {
3123     var aInitiator = a._request.initiatorInfo();
3124     var bInitiator = b._request.initiatorInfo();
3125
3126     if (aInitiator.type < bInitiator.type)
3127         return -1;
3128     if (aInitiator.type > bInitiator.type)
3129         return 1;
3130
3131     if (typeof aInitiator.__source === "undefined")
3132         aInitiator.__source = WebInspector.displayNameForURL(aInitiator.url);
3133     if (typeof bInitiator.__source === "undefined")
3134         bInitiator.__source = WebInspector.displayNameForURL(bInitiator.url);
3135
3136     if (aInitiator.__source < bInitiator.__source)
3137         return -1;
3138     if (aInitiator.__source > bInitiator.__source)
3139         return 1;
3140
3141     if (aInitiator.lineNumber < bInitiator.lineNumber)
3142         return -1;
3143     if (aInitiator.lineNumber > bInitiator.lineNumber)
3144         return 1;
3145
3146     if (aInitiator.columnNumber < bInitiator.columnNumber)
3147         return -1;
3148     if (aInitiator.columnNumber > bInitiator.columnNumber)
3149         return 1;
3150
3151     return a._request.indentityCompare(b._request);
3152 }
3153
3154 /**
3155  * @param {!WebInspector.NetworkDataGridNode} a
3156  * @param {!WebInspector.NetworkDataGridNode} b
3157  * @return {number}
3158  */
3159 WebInspector.NetworkDataGridNode.RequestCookiesCountComparator = function(a, b)
3160 {
3161     var aScore = a._request.requestCookies ? a._request.requestCookies.length : 0;
3162     var bScore = b._request.requestCookies ? b._request.requestCookies.length : 0;
3163     return (aScore - bScore) || a._request.indentityCompare(b._request);
3164 }
3165
3166 /**
3167  * @param {!WebInspector.NetworkDataGridNode} a
3168  * @param {!WebInspector.NetworkDataGridNode} b
3169  * @return {number}
3170  */
3171 WebInspector.NetworkDataGridNode.ResponseCookiesCountComparator = function(a, b)
3172 {
3173     var aScore = a._request.responseCookies ? a._request.responseCookies.length : 0;
3174     var bScore = b._request.responseCookies ? b._request.responseCookies.length : 0;
3175     return (aScore - bScore) || a._request.indentityCompare(b._request);
3176 }
3177
3178 /**
3179  * @param {string} propertyName
3180  * @param {boolean} revert
3181  * @param {!WebInspector.NetworkDataGridNode} a
3182  * @param {!WebInspector.NetworkDataGridNode} b
3183  * @return {number}
3184  */
3185 WebInspector.NetworkDataGridNode.RequestPropertyComparator = function(propertyName, revert, a, b)
3186 {
3187     var aValue = a._request[propertyName];
3188     var bValue = b._request[propertyName];
3189     if (aValue > bValue)
3190         return revert ? -1 : 1;
3191     if (bValue > aValue)
3192         return revert ? 1 : -1;
3193     return a._request.indentityCompare(b._request);
3194 }
3195
3196 WebInspector.NetworkPanel.show = function()
3197 {
3198     WebInspector.inspectorView.setCurrentPanel(WebInspector.NetworkPanel._instance());
3199 }
3200
3201 /**
3202  * @return {!WebInspector.NetworkPanel}
3203  */
3204 WebInspector.NetworkPanel._instance = function()
3205 {
3206     if (!WebInspector.NetworkPanel._instanceObject)
3207         WebInspector.NetworkPanel._instanceObject = new WebInspector.NetworkPanel();
3208     return WebInspector.NetworkPanel._instanceObject;
3209 }
3210
3211 /**
3212  * @constructor
3213  * @implements {WebInspector.PanelFactory}
3214  */
3215 WebInspector.NetworkPanelFactory = function()
3216 {
3217 }
3218
3219 WebInspector.NetworkPanelFactory.prototype = {
3220     /**
3221      * @return {!WebInspector.Panel}
3222      */
3223     createPanel: function()
3224     {
3225         return WebInspector.NetworkPanel._instance();
3226     }
3227 }
3228
3229 /**
3230  * @constructor
3231  */
3232 WebInspector.HARWriter = function()
3233 {
3234 }
3235
3236 WebInspector.HARWriter.prototype = {
3237     /**
3238      * @param {!WebInspector.OutputStream} stream
3239      * @param {!Array.<!WebInspector.NetworkRequest>} requests
3240      * @param {!WebInspector.Progress} progress
3241      */
3242     write: function(stream, requests, progress)
3243     {
3244         this._stream = stream;
3245         this._harLog = (new WebInspector.HARLog(requests)).build();
3246         this._pendingRequests = 1; // Guard against completing resource transfer before all requests are made.
3247         var entries = this._harLog.entries;
3248         for (var i = 0; i < entries.length; ++i) {
3249             var content = requests[i].content;
3250             if (typeof content === "undefined" && requests[i].finished) {
3251                 ++this._pendingRequests;
3252                 requests[i].requestContent(this._onContentAvailable.bind(this, entries[i], requests[i]));
3253             } else if (content !== null)
3254                 this._setEntryContent(entries[i], requests[i]);
3255         }
3256         var compositeProgress = new WebInspector.CompositeProgress(progress);
3257         this._writeProgress = compositeProgress.createSubProgress();
3258         if (--this._pendingRequests) {
3259             this._requestsProgress = compositeProgress.createSubProgress();
3260             this._requestsProgress.setTitle(WebInspector.UIString("Collecting content…"));
3261             this._requestsProgress.setTotalWork(this._pendingRequests);
3262         } else
3263             this._beginWrite();
3264     },
3265
3266     /**
3267      * @param {!Object} entry
3268      * @param {!WebInspector.NetworkRequest} request
3269      */
3270     _setEntryContent: function(entry, request)
3271     {
3272         if (request.content !== null)
3273             entry.response.content.text = request.content;
3274         if (request.contentEncoded)
3275             entry.response.content.encoding = "base64";
3276     },
3277
3278     /**
3279      * @param {!Object} entry
3280      * @param {!WebInspector.NetworkRequest} request
3281      * @param {?string} content
3282      */
3283     _onContentAvailable: function(entry, request, content)
3284     {
3285         this._setEntryContent(entry, request);
3286         if (this._requestsProgress)
3287             this._requestsProgress.worked();
3288         if (!--this._pendingRequests) {
3289             this._requestsProgress.done();
3290             this._beginWrite();
3291         }
3292     },
3293
3294     _beginWrite: function()
3295     {
3296         const jsonIndent = 2;
3297         this._text = JSON.stringify({log: this._harLog}, null, jsonIndent);
3298         this._writeProgress.setTitle(WebInspector.UIString("Writing file…"));
3299         this._writeProgress.setTotalWork(this._text.length);
3300         this._bytesWritten = 0;
3301         this._writeNextChunk(this._stream);
3302     },
3303
3304     /**
3305      * @param {!WebInspector.OutputStream} stream
3306      * @param {string=} error
3307      */
3308     _writeNextChunk: function(stream, error)
3309     {
3310         if (this._bytesWritten >= this._text.length || error) {
3311             stream.close();
3312             this._writeProgress.done();
3313             return;
3314         }
3315         const chunkSize = 100000;
3316         var text = this._text.substring(this._bytesWritten, this._bytesWritten + chunkSize);
3317         this._bytesWritten += text.length;
3318         stream.write(text, this._writeNextChunk.bind(this));
3319         this._writeProgress.setWorked(this._bytesWritten);
3320     }
3321 }