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