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