b19042a8dc51c95bb56ce649621f225780b01d71
[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)
929             return null;
930         var request = anchor.parentElement.request;
931         return request && request.timing ? anchor : null;
932     },
933
934     /**
935      * @param {Element} anchor
936      * @param {WebInspector.Popover} popover
937      */
938     _showPopover: function(anchor, popover)
939     {
940         var request = anchor.parentElement.request;
941         var tableElement = WebInspector.RequestTimingView.createTimingTable(request);
942         popover.show(tableElement, anchor);
943     },
944
945     _contextMenu: function(event)
946     {
947         var contextMenu = new WebInspector.ContextMenu(event);
948         var gridNode = this._dataGrid.dataGridNodeFromNode(event.target);
949         var request = gridNode && gridNode._request;
950
951         if (request) {
952             contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, request.url, false));
953             contextMenu.appendSeparator();
954             contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), this._copyLocation.bind(this, request));
955             if (request.requestHeadersText)
956                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy request headers" : "Copy Request Headers"), this._copyRequestHeaders.bind(this, request));
957             if (request.responseHeadersText)
958                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy response headers" : "Copy Response Headers"), this._copyResponseHeaders.bind(this, request));
959         }
960         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy all as HAR" : "Copy All as HAR"), this._copyAll.bind(this));
961
962         if (InspectorFrontendHost.canSave()) {
963             contextMenu.appendSeparator();
964             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as HAR with content" : "Save as HAR with Content"), this._exportAll.bind(this));
965         }
966
967         if (this._canClearBrowserCache || this._canClearBrowserCookies)
968             contextMenu.appendSeparator();
969         if (this._canClearBrowserCache)
970             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cache" : "Clear Browser Cache"), this._clearBrowserCache.bind(this));
971         if (this._canClearBrowserCookies)
972             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cookies" : "Clear Browser Cookies"), this._clearBrowserCookies.bind(this));
973
974
975         if (request && request.type === WebInspector.resourceTypes.XHR) {
976             contextMenu.appendSeparator();
977             contextMenu.appendItem(WebInspector.UIString("Replay XHR"), this._replayXHR.bind(this, request.requestId));
978             contextMenu.appendSeparator();
979         }
980
981         contextMenu.show();
982     },
983     
984     _replayXHR: function(requestId)
985     {
986         NetworkAgent.replayXHR(requestId);
987     },
988     
989
990     _copyAll: function()
991     {
992         var harArchive = {
993             log: (new WebInspector.HARLog(this._requests)).build()
994         };
995         InspectorFrontendHost.copyText(JSON.stringify(harArchive, null, 2));
996     },
997
998     _copyLocation: function(request)
999     {
1000         InspectorFrontendHost.copyText(request.url);
1001     },
1002
1003     _copyRequestHeaders: function(request)
1004     {
1005         InspectorFrontendHost.copyText(request.requestHeadersText);
1006     },
1007
1008     _copyResponseHeaders: function(request)
1009     {
1010         InspectorFrontendHost.copyText(request.responseHeadersText);
1011     },
1012
1013     _exportAll: function()
1014     {
1015         var filename = WebInspector.inspectedPageDomain + ".har";
1016         var stream = new WebInspector.FileOutputStream();
1017         stream.open(filename, openCallback.bind(this));
1018         function openCallback()
1019         {
1020             var progressIndicator = new WebInspector.ProgressIndicator();
1021             this._progressBarContainer.appendChild(progressIndicator.element);
1022             var harWriter = new WebInspector.HARWriter();
1023             harWriter.write(stream, this._requests, progressIndicator);
1024         }
1025     },
1026
1027     _clearBrowserCache: function(event)
1028     {
1029         if (confirm(WebInspector.UIString("Are you sure you want to clear browser cache?")))
1030             NetworkAgent.clearBrowserCache();
1031     },
1032
1033     _clearBrowserCookies: function(event)
1034     {
1035         if (confirm(WebInspector.UIString("Are you sure you want to clear browser cookies?")))
1036             NetworkAgent.clearBrowserCookies();
1037     },
1038
1039     _updateOffscreenRows: function()
1040     {
1041         var dataTableBody = this._dataGrid.dataTableBody;
1042         var rows = dataTableBody.children;
1043         var recordsCount = rows.length;
1044         if (recordsCount < 2)
1045             return;  // Filler row only.
1046
1047         var visibleTop = this._dataGrid.scrollContainer.scrollTop;
1048         var visibleBottom = visibleTop + this._dataGrid.scrollContainer.offsetHeight;
1049
1050         var rowHeight = 0;
1051
1052         // Filler is at recordsCount - 1.
1053         var unfilteredRowIndex = 0;
1054         for (var i = 0; i < recordsCount - 1; ++i) {
1055             var row = rows[i];
1056
1057             var dataGridNode = this._dataGrid.dataGridNodeFromNode(row);
1058             if (dataGridNode.isFilteredOut()) {
1059                 row.removeStyleClass("offscreen");
1060                 continue;
1061             }
1062
1063             if (!rowHeight)
1064                 rowHeight = row.offsetHeight;
1065
1066             var rowIsVisible = unfilteredRowIndex * rowHeight < visibleBottom && (unfilteredRowIndex + 1) * rowHeight > visibleTop;
1067             if (rowIsVisible !== row.rowIsVisible) {
1068                 if (rowIsVisible)
1069                     row.removeStyleClass("offscreen");
1070                 else
1071                     row.addStyleClass("offscreen");
1072                 row.rowIsVisible = rowIsVisible;
1073             }
1074             unfilteredRowIndex++;
1075         }
1076     },
1077
1078     _matchRequest: function(request)
1079     {
1080         if (!this._searchRegExp)
1081             return -1;
1082
1083         if (!request.name().match(this._searchRegExp) && !request.path().match(this._searchRegExp))
1084             return -1;
1085
1086         if (request.requestId in this._matchedRequestsMap)
1087             return this._matchedRequestsMap[request.requestId];
1088
1089         var matchedRequestIndex = this._matchedRequests.length;
1090         this._matchedRequestsMap[request.requestId] = matchedRequestIndex;
1091         this._matchedRequests.push(request.requestId);
1092
1093         return matchedRequestIndex;
1094     },
1095
1096     _clearSearchMatchedList: function()
1097     {
1098         delete this._searchRegExp;
1099         this._matchedRequests = [];
1100         this._matchedRequestsMap = {};
1101         this._removeAllHighlights();
1102     },
1103
1104     _updateSearchMatchedListAfterRequestIdChanged: function(oldRequestId, newRequestId)
1105     {
1106         var requestIndex = this._matchedRequestsMap[oldRequestId];
1107         if (requestIndex) {
1108             delete this._matchedRequestsMap[oldRequestId];
1109             this._matchedRequestsMap[newRequestId] = requestIndex;
1110             this._matchedRequests[requestIndex] = newRequestId;
1111         }
1112     },
1113
1114     _updateHighlightIfMatched: function(request)
1115     {
1116         var matchedRequestIndex = this._matchRequest(request);
1117         if (matchedRequestIndex === -1)
1118             return;
1119
1120         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedRequests.length);
1121
1122         if (this._currentMatchedRequestIndex !== -1 && this._currentMatchedRequestIndex !== matchedRequestIndex)
1123             return;
1124
1125         this._highlightNthMatchedRequestForSearch(matchedRequestIndex, false);
1126     },
1127
1128     _removeAllHighlights: function()
1129     {
1130         for (var i = 0; i < this._highlightedSubstringChanges.length; ++i)
1131             WebInspector.revertDomChanges(this._highlightedSubstringChanges[i]);
1132         this._highlightedSubstringChanges = [];
1133     },
1134
1135     /**
1136      * @param {WebInspector.NetworkRequest} request
1137      * @param {boolean} reveal
1138      * @param {RegExp=} regExp
1139      */
1140     _highlightMatchedRequest: function(request, reveal, regExp)
1141     {
1142         var node = this._requestGridNode(request);
1143         if (!node)
1144             return;
1145
1146         var nameMatched = request.name().match(regExp);
1147         var pathMatched = request.path().match(regExp);
1148         if (!nameMatched && pathMatched && !this._largerRequestsButton.toggled)
1149             this._toggleLargerRequests();
1150         var highlightedSubstringChanges = node._highlightMatchedSubstring(regExp);
1151         this._highlightedSubstringChanges.push(highlightedSubstringChanges);
1152         if (reveal)
1153             node.reveal();
1154     },
1155
1156     /**
1157      * @param {number} matchedRequestIndex
1158      * @param {boolean} reveal
1159      */
1160     _highlightNthMatchedRequestForSearch: function(matchedRequestIndex, reveal)
1161     {
1162         var request = this.requestById(this._matchedRequests[matchedRequestIndex]);
1163         if (!request)
1164             return;
1165         this._removeAllHighlights();
1166         this._highlightMatchedRequest(request, reveal, this._searchRegExp);
1167         var node = this._requestGridNode(request);
1168         if (node)
1169             this._currentMatchedRequestIndex = matchedRequestIndex;
1170
1171         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._currentMatchedRequestIndex);
1172     },
1173
1174     performSearch: function(searchQuery)
1175     {
1176         var newMatchedRequestIndex = 0;
1177         var currentMatchedRequestId;
1178         if (this._currentMatchedRequestIndex !== -1)
1179             currentMatchedRequestId = this._matchedRequests[this._currentMatchedRequestIndex];
1180
1181         this._clearSearchMatchedList();
1182         this._searchRegExp = createPlainTextSearchRegex(searchQuery, "i");
1183
1184         var childNodes = this._dataGrid.dataTableBody.childNodes;
1185         var requestNodes = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); // drop the filler row.
1186
1187         for (var i = 0; i < requestNodes.length; ++i) {
1188             var dataGridNode = this._dataGrid.dataGridNodeFromNode(requestNodes[i]);
1189             if (dataGridNode.isFilteredOut())
1190                 continue;
1191             if (this._matchRequest(dataGridNode._request) !== -1 && dataGridNode._request.requestId === currentMatchedRequestId)
1192                 newMatchedRequestIndex = this._matchedRequests.length - 1;
1193         }
1194
1195         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedRequests.length);
1196         this._highlightNthMatchedRequestForSearch(newMatchedRequestIndex, false);
1197     },
1198
1199     /**
1200      * @param {!WebInspector.NetworkDataGridNode} node
1201      */
1202     _applyFilter: function(node) {
1203         var filter = this._filterRegExp;
1204         var request = node._request;
1205         if (!filter)
1206             return;
1207         if (filter.test(request.name()) || filter.test(request.path()))
1208             this._highlightMatchedRequest(request, false, filter);
1209         else {
1210             node.element.addStyleClass("filtered-out");
1211             this._filteredOutRequests.put(request, true);
1212         }
1213     },
1214
1215     /**
1216      * @param {string} query
1217      */
1218     performFilter: function(query)
1219     {
1220         this._removeAllHighlights();
1221         this._filteredOutRequests.clear();
1222         delete this._filterRegExp;
1223         if (query)
1224             this._filterRegExp = createPlainTextSearchRegex(query, "i");
1225
1226         var nodes = this._dataGrid.rootNode().children;
1227         for (var i = 0; i < nodes.length; ++i) {
1228             nodes[i].element.removeStyleClass("filtered-out");
1229             this._applyFilter(nodes[i]);
1230         }
1231         this._updateSummaryBar();
1232         this._updateOffscreenRows();
1233     },
1234     
1235     jumpToPreviousSearchResult: function()
1236     {
1237         if (!this._matchedRequests.length)
1238             return;
1239         this._highlightNthMatchedRequestForSearch((this._currentMatchedRequestIndex + this._matchedRequests.length - 1) % this._matchedRequests.length, true);
1240     },
1241
1242     jumpToNextSearchResult: function()
1243     {
1244         if (!this._matchedRequests.length)
1245             return;
1246         this._highlightNthMatchedRequestForSearch((this._currentMatchedRequestIndex + 1) % this._matchedRequests.length, true);
1247     },
1248
1249     searchCanceled: function()
1250     {
1251         this._clearSearchMatchedList();
1252         this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, 0);
1253     },
1254
1255     revealAndHighlightRequest: function(request)
1256     {
1257         this._removeAllNodeHighlights();
1258
1259         var node = this._requestGridNode(request);
1260         if (node) {
1261             this._dataGrid.element.focus();
1262             node.reveal();
1263             this._highlightNode(node);
1264         }
1265     },
1266
1267     _removeAllNodeHighlights: function()
1268     {
1269         if (this._highlightedNode) {
1270             this._highlightedNode.element.removeStyleClass("highlighted-row");
1271             delete this._highlightedNode;
1272         }
1273     },
1274
1275     _highlightNode: function(node)
1276     {
1277         node.element.addStyleClass("highlighted-row");
1278         this._highlightedNode = node;
1279     },
1280
1281     __proto__: WebInspector.View.prototype
1282 }
1283
1284
1285 WebInspector.NetworkLogView.EventTypes = {
1286     ViewCleared: "ViewCleared",
1287     RowSizeChanged: "RowSizeChanged",
1288     RequestSelected: "RequestSelected",
1289     SearchCountUpdated: "SearchCountUpdated",
1290     SearchIndexUpdated: "SearchIndexUpdated"
1291 };
1292
1293 /**
1294  * @constructor
1295  * @extends {WebInspector.Panel}
1296  * @implements {WebInspector.ContextMenu.Provider}
1297  */
1298 WebInspector.NetworkPanel = function()
1299 {
1300     WebInspector.Panel.call(this, "network");
1301     this.registerRequiredCSS("networkPanel.css");
1302
1303     this.createSidebarView();
1304     this.splitView.hideMainElement();
1305
1306     this._networkLogView = new WebInspector.NetworkLogView();
1307     this._networkLogView.show(this.sidebarElement);
1308
1309     this._viewsContainerElement = this.splitView.mainElement;
1310     this._viewsContainerElement.id = "network-views";
1311     this._viewsContainerElement.addStyleClass("hidden");
1312     if (!this._networkLogView.useLargeRows)
1313         this._viewsContainerElement.addStyleClass("small");
1314
1315     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ViewCleared, this._onViewCleared, this);
1316     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, this._onRowSizeChanged, this);
1317     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RequestSelected, this._onRequestSelected, this);
1318     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._onSearchCountUpdated, this);
1319     this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._onSearchIndexUpdated, this);
1320
1321     this._closeButtonElement = document.createElement("button");
1322     this._closeButtonElement.id = "network-close-button";
1323     this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false);
1324     this._viewsContainerElement.appendChild(this._closeButtonElement);
1325
1326     function viewGetter()
1327     {
1328         return this.visibleView;
1329     }
1330     WebInspector.GoToLineDialog.install(this, viewGetter.bind(this));
1331 }
1332
1333 WebInspector.NetworkPanel.prototype = {
1334     get statusBarItems()
1335     {
1336         return this._networkLogView.statusBarItems;
1337     },
1338
1339     elementsToRestoreScrollPositionsFor: function()
1340     {
1341         return this._networkLogView.elementsToRestoreScrollPositionsFor();
1342     },
1343
1344     // FIXME: only used by the layout tests, should not be exposed.
1345     _reset: function()
1346     {
1347         this._networkLogView._reset();
1348     },
1349
1350     handleShortcut: function(event)
1351     {
1352         if (this._viewingRequestMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
1353             this._toggleGridMode();
1354             event.handled = true;
1355             return;
1356         }
1357
1358         WebInspector.Panel.prototype.handleShortcut.call(this, event);
1359     },
1360
1361     wasShown: function()
1362     {
1363         WebInspector.Panel.prototype.wasShown.call(this);
1364     },
1365
1366     get requests()
1367     {
1368         return this._networkLogView.requests;
1369     },
1370
1371     requestById: function(id)
1372     {
1373         return this._networkLogView.requestById(id);
1374     },
1375
1376     _requestByAnchor: function(anchor)
1377     {
1378         return anchor.requestId ? this.requestById(anchor.requestId) : this._networkLogView._requestsByURL[anchor.href];
1379     },
1380
1381     canShowAnchorLocation: function(anchor)
1382     {
1383         return !!this._requestByAnchor(anchor);
1384     },
1385
1386     showAnchorLocation: function(anchor)
1387     {
1388         var request = this._requestByAnchor(anchor);
1389         this.revealAndHighlightRequest(request)
1390     },
1391
1392     revealAndHighlightRequest: function(request)
1393     {
1394         this._toggleGridMode();
1395         if (request)
1396             this._networkLogView.revealAndHighlightRequest(request);
1397     },
1398
1399     _onViewCleared: function(event)
1400     {
1401         this._closeVisibleRequest();
1402         this._toggleGridMode();
1403         this._viewsContainerElement.removeChildren();
1404         this._viewsContainerElement.appendChild(this._closeButtonElement);
1405     },
1406
1407     _onRowSizeChanged: function(event)
1408     {
1409         if (event.data.largeRows)
1410             this._viewsContainerElement.removeStyleClass("small");
1411         else
1412             this._viewsContainerElement.addStyleClass("small");
1413     },
1414
1415     _onSearchCountUpdated: function(event)
1416     {
1417         WebInspector.searchController.updateSearchMatchesCount(event.data, this);
1418     },
1419
1420     _onSearchIndexUpdated: function(event)
1421     {
1422         WebInspector.searchController.updateCurrentMatchIndex(event.data, this);
1423     },
1424
1425     _onRequestSelected: function(event)
1426     {
1427         this._showRequest(event.data);
1428     },
1429
1430     _showRequest: function(request)
1431     {
1432         if (!request)
1433             return;
1434
1435         this._toggleViewingRequestMode();
1436
1437         if (this.visibleView) {
1438             this.visibleView.detach();
1439             delete this.visibleView;
1440         }
1441
1442         var view = new WebInspector.NetworkItemView(request);
1443         view.show(this._viewsContainerElement);
1444         this.visibleView = view;
1445     },
1446
1447     _closeVisibleRequest: function()
1448     {
1449         this.element.removeStyleClass("viewing-resource");
1450
1451         if (this.visibleView) {
1452             this.visibleView.detach();
1453             delete this.visibleView;
1454         }
1455     },
1456
1457     _toggleGridMode: function()
1458     {
1459         if (this._viewingRequestMode) {
1460             this._viewingRequestMode = false;
1461             this.element.removeStyleClass("viewing-resource");
1462             this.splitView.hideMainElement();
1463         }
1464
1465         this._networkLogView.switchToDetailedView();
1466         this._networkLogView.allowPopover = true;
1467         this._networkLogView._allowRequestSelection = false;
1468     },
1469
1470     _toggleViewingRequestMode: function()
1471     {
1472         if (this._viewingRequestMode)
1473             return;
1474         this._viewingRequestMode = true;
1475
1476         this.element.addStyleClass("viewing-resource");
1477         this.splitView.showMainElement();
1478         this._networkLogView.allowPopover = false;
1479         this._networkLogView._allowRequestSelection = true;
1480         this._networkLogView.switchToBriefView();
1481     },
1482
1483     /**
1484      * @param {string} searchQuery
1485      */
1486     performSearch: function(searchQuery)
1487     {
1488         this._networkLogView.performSearch(searchQuery);
1489     },
1490
1491     /**
1492      * @return {boolean}
1493      */
1494     canFilter: function()
1495     {
1496         return true;
1497     },
1498
1499     /**
1500      * @param {string} query
1501      */    
1502     performFilter: function(query)
1503     {
1504         this._networkLogView.performFilter(query);
1505     },
1506
1507     jumpToPreviousSearchResult: function()
1508     {
1509         this._networkLogView.jumpToPreviousSearchResult();
1510     },
1511
1512     jumpToNextSearchResult: function()
1513     {
1514         this._networkLogView.jumpToNextSearchResult();
1515     },
1516
1517     searchCanceled: function()
1518     {
1519         this._networkLogView.searchCanceled();
1520     },
1521
1522     /** 
1523      * @param {WebInspector.ContextMenu} contextMenu
1524      * @param {Object} target
1525      */
1526     appendApplicableItems: function(event, contextMenu, target)
1527     {
1528         if (!(target instanceof WebInspector.NetworkRequest))
1529             return;
1530         if (this.visibleView && this.visibleView.isShowing() && this.visibleView.request() === target)
1531             return;
1532
1533         function reveal()
1534         {
1535             WebInspector.inspectorView.setCurrentPanel(this);
1536             this.revealAndHighlightRequest(/** @type {WebInspector.NetworkRequest} */ (target));
1537         }
1538         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in network panel" : "Reveal in Network Panel"), reveal.bind(this));
1539     },
1540
1541     __proto__: WebInspector.Panel.prototype
1542 }
1543
1544 /**
1545  * @constructor
1546  * @implements {WebInspector.TimelineGrid.Calculator}
1547  */
1548 WebInspector.NetworkBaseCalculator = function()
1549 {
1550 }
1551
1552 WebInspector.NetworkBaseCalculator.prototype = {
1553     computePosition: function(time)
1554     {
1555         return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea;
1556     },
1557
1558     computeBarGraphPercentages: function(item)
1559     {
1560         return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan()) * 100};
1561     },
1562
1563     computeBarGraphLabels: function(item)
1564     {
1565         const label = this.formatTime(this._value(item));
1566         return {left: label, right: label, tooltip: label};
1567     },
1568
1569     boundarySpan: function()
1570     {
1571         return this._maximumBoundary - this._minimumBoundary;
1572     },
1573
1574     updateBoundaries: function(item)
1575     {
1576         this._minimumBoundary = 0;
1577
1578         var value = this._value(item);
1579         if (typeof this._maximumBoundary === "undefined" || value > this._maximumBoundary) {
1580             this._maximumBoundary = value;
1581             return true;
1582         }
1583         return false;
1584     },
1585
1586     reset: function()
1587     {
1588         delete this._minimumBoundary;
1589         delete this._maximumBoundary;
1590     },
1591
1592     maximumBoundary: function()
1593     {
1594         return this._maximumBoundary;
1595     },
1596
1597     minimumBoundary: function()
1598     {
1599         return this._minimumBoundary;
1600     },
1601
1602     _value: function(item)
1603     {
1604         return 0;
1605     },
1606
1607     formatTime: function(value)
1608     {
1609         return value.toString();
1610     },
1611
1612     setDisplayWindow: function(clientWidth)
1613     {
1614         this._workingArea = clientWidth;
1615         this.paddingLeft = 0;
1616     }
1617 }
1618
1619 /**
1620  * @constructor
1621  * @extends {WebInspector.NetworkBaseCalculator}
1622  */
1623 WebInspector.NetworkTimeCalculator = function(startAtZero)
1624 {
1625     WebInspector.NetworkBaseCalculator.call(this);
1626     this.startAtZero = startAtZero;
1627 }
1628
1629 WebInspector.NetworkTimeCalculator.prototype = {
1630     computeBarGraphPercentages: function(request)
1631     {
1632         if (request.startTime !== -1)
1633             var start = ((request.startTime - this._minimumBoundary) / this.boundarySpan()) * 100;
1634         else
1635             var start = 0;
1636
1637         if (request.responseReceivedTime !== -1)
1638             var middle = ((request.responseReceivedTime - this._minimumBoundary) / this.boundarySpan()) * 100;
1639         else
1640             var middle = (this.startAtZero ? start : 100);
1641
1642         if (request.endTime !== -1)
1643             var end = ((request.endTime - this._minimumBoundary) / this.boundarySpan()) * 100;
1644         else
1645             var end = (this.startAtZero ? middle : 100);
1646
1647         if (this.startAtZero) {
1648             end -= start;
1649             middle -= start;
1650             start = 0;
1651         }
1652
1653         return {start: start, middle: middle, end: end};
1654     },
1655
1656     computePercentageFromEventTime: function(eventTime)
1657     {
1658         // This function computes a percentage in terms of the total loading time
1659         // of a specific event. If startAtZero is set, then this is useless, and we
1660         // want to return 0.
1661         if (eventTime !== -1 && !this.startAtZero)
1662             return ((eventTime - this._minimumBoundary) / this.boundarySpan()) * 100;
1663
1664         return 0;
1665     },
1666
1667     updateBoundariesForEventTime: function(eventTime)
1668     {
1669         if (eventTime === -1 || this.startAtZero)
1670             return false;
1671
1672         if (typeof this._maximumBoundary === "undefined" || eventTime > this._maximumBoundary) {
1673             this._maximumBoundary = eventTime;
1674             return true;
1675         }
1676         return false;
1677     },
1678
1679     computeBarGraphLabels: function(request)
1680     {
1681         var rightLabel = "";
1682         if (request.responseReceivedTime !== -1 && request.endTime !== -1)
1683             rightLabel = this.formatTime(request.endTime - request.responseReceivedTime);
1684
1685         var hasLatency = request.latency > 0;
1686         if (hasLatency)
1687             var leftLabel = this.formatTime(request.latency);
1688         else
1689             var leftLabel = rightLabel;
1690
1691         if (request.timing)
1692             return {left: leftLabel, right: rightLabel};
1693
1694         if (hasLatency && rightLabel) {
1695             var total = this.formatTime(request.duration);
1696             var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
1697         } else if (hasLatency)
1698             var tooltip = WebInspector.UIString("%s latency", leftLabel);
1699         else if (rightLabel)
1700             var tooltip = WebInspector.UIString("%s download", rightLabel);
1701
1702         if (request.cached)
1703             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1704         return {left: leftLabel, right: rightLabel, tooltip: tooltip};
1705     },
1706
1707     updateBoundaries: function(request)
1708     {
1709         var didChange = false;
1710
1711         var lowerBound;
1712         if (this.startAtZero)
1713             lowerBound = 0;
1714         else
1715             lowerBound = this._lowerBound(request);
1716
1717         if (lowerBound !== -1 && (typeof this._minimumBoundary === "undefined" || lowerBound < this._minimumBoundary)) {
1718             this._minimumBoundary = lowerBound;
1719             didChange = true;
1720         }
1721
1722         var upperBound = this._upperBound(request);
1723         if (upperBound !== -1 && (typeof this._maximumBoundary === "undefined" || upperBound > this._maximumBoundary)) {
1724             this._maximumBoundary = upperBound;
1725             didChange = true;
1726         }
1727
1728         return didChange;
1729     },
1730
1731     formatTime: function(value)
1732     {
1733         return Number.secondsToString(value);
1734     },
1735
1736     _lowerBound: function(request)
1737     {
1738         return 0;
1739     },
1740
1741     _upperBound: function(request)
1742     {
1743         return 0;
1744     },
1745
1746     __proto__: WebInspector.NetworkBaseCalculator.prototype
1747 }
1748
1749 /**
1750  * @constructor
1751  * @extends {WebInspector.NetworkTimeCalculator}
1752  */
1753 WebInspector.NetworkTransferTimeCalculator = function()
1754 {
1755     WebInspector.NetworkTimeCalculator.call(this, false);
1756 }
1757
1758 WebInspector.NetworkTransferTimeCalculator.prototype = {
1759     formatTime: function(value)
1760     {
1761         return Number.secondsToString(value);
1762     },
1763
1764     _lowerBound: function(request)
1765     {
1766         return request.startTime;
1767     },
1768
1769     _upperBound: function(request)
1770     {
1771         return request.endTime;
1772     },
1773
1774     __proto__: WebInspector.NetworkTimeCalculator.prototype
1775 }
1776
1777 /**
1778  * @constructor
1779  * @extends {WebInspector.NetworkTimeCalculator}
1780  */
1781 WebInspector.NetworkTransferDurationCalculator = function()
1782 {
1783     WebInspector.NetworkTimeCalculator.call(this, true);
1784 }
1785
1786 WebInspector.NetworkTransferDurationCalculator.prototype = {
1787     formatTime: function(value)
1788     {
1789         return Number.secondsToString(value);
1790     },
1791
1792     _upperBound: function(request)
1793     {
1794         return request.duration;
1795     },
1796
1797     __proto__: WebInspector.NetworkTimeCalculator.prototype
1798 }
1799
1800 /**
1801  * @constructor
1802  * @extends {WebInspector.DataGridNode}
1803  */
1804 WebInspector.NetworkDataGridNode = function(parentView, request)
1805 {
1806     WebInspector.DataGridNode.call(this, {});
1807     this._parentView = parentView;
1808     this._request = request;
1809 }
1810
1811 WebInspector.NetworkDataGridNode.prototype = {
1812     createCells: function()
1813     {
1814         // Out of sight, out of mind: create nodes offscreen to save on render tree update times when running updateOffscreenRows()
1815         this._element.addStyleClass("offscreen");
1816         this._nameCell = this._createDivInTD("name");
1817         this._methodCell = this._createDivInTD("method");
1818         this._statusCell = this._createDivInTD("status");
1819         this._typeCell = this._createDivInTD("type");
1820         this._initiatorCell = this._createDivInTD("initiator");
1821         this._sizeCell = this._createDivInTD("size");
1822         this._timeCell = this._createDivInTD("time");
1823         this._createTimelineCell();
1824         this._nameCell.addEventListener("click", this.select.bind(this), false);
1825         this._nameCell.addEventListener("dblclick", this._openInNewTab.bind(this), false);
1826     },
1827
1828     isFilteredOut: function()
1829     {
1830         if (this._parentView._filteredOutRequests.get(this._request))
1831             return true;
1832         if (!this._parentView._hiddenCategories.all)
1833             return false;
1834         return this._request.type.name() in this._parentView._hiddenCategories;
1835     },
1836
1837     select: function()
1838     {
1839         this._parentView.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RequestSelected, this._request);
1840         WebInspector.DataGridNode.prototype.select.apply(this, arguments);
1841
1842         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
1843             action: WebInspector.UserMetrics.UserActionNames.NetworkRequestSelected,
1844             url: this._request.url
1845         });
1846     },
1847
1848     _highlightMatchedSubstring: function(regexp)
1849     {
1850         var domChanges = [];
1851         var matchInfo = this._element.textContent.match(regexp);
1852         if (matchInfo)
1853             WebInspector.highlightSearchResult(this._nameCell, matchInfo.index, matchInfo[0].length, domChanges);
1854         return domChanges;
1855     },
1856
1857     _openInNewTab: function()
1858     {
1859         InspectorFrontendHost.openInNewTab(this._request.url);
1860     },
1861
1862     get selectable()
1863     {
1864         return this._parentView._allowRequestSelection && !this.isFilteredOut();
1865     },
1866
1867     _createDivInTD: function(columnIdentifier)
1868     {
1869         var td = document.createElement("td");
1870         td.className = columnIdentifier + "-column";
1871         var div = document.createElement("div");
1872         td.appendChild(div);
1873         this._element.appendChild(td);
1874         return div;
1875     },
1876
1877     _createTimelineCell: function()
1878     {
1879         this._graphElement = document.createElement("div");
1880         this._graphElement.className = "network-graph-side";
1881
1882         this._barAreaElement = document.createElement("div");
1883         //    this._barAreaElement.className = "network-graph-bar-area hidden";
1884         this._barAreaElement.className = "network-graph-bar-area";
1885         this._barAreaElement.request = this._request;
1886         this._graphElement.appendChild(this._barAreaElement);
1887
1888         this._barLeftElement = document.createElement("div");
1889         this._barLeftElement.className = "network-graph-bar waiting";
1890         this._barAreaElement.appendChild(this._barLeftElement);
1891
1892         this._barRightElement = document.createElement("div");
1893         this._barRightElement.className = "network-graph-bar";
1894         this._barAreaElement.appendChild(this._barRightElement);
1895
1896
1897         this._labelLeftElement = document.createElement("div");
1898         this._labelLeftElement.className = "network-graph-label waiting";
1899         this._barAreaElement.appendChild(this._labelLeftElement);
1900
1901         this._labelRightElement = document.createElement("div");
1902         this._labelRightElement.className = "network-graph-label";
1903         this._barAreaElement.appendChild(this._labelRightElement);
1904
1905         this._graphElement.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false);
1906
1907         this._timelineCell = document.createElement("td");
1908         this._timelineCell.className = "timeline-column";
1909         this._element.appendChild(this._timelineCell);
1910         this._timelineCell.appendChild(this._graphElement);
1911     },
1912
1913     refreshRequest: function()
1914     {
1915         this._refreshNameCell();
1916
1917         this._methodCell.setTextAndTitle(this._request.requestMethod);
1918
1919         this._refreshStatusCell();
1920         this._refreshTypeCell();
1921         this._refreshInitiatorCell();
1922         this._refreshSizeCell();
1923         this._refreshTimeCell();
1924
1925         if (this._request.cached)
1926             this._graphElement.addStyleClass("resource-cached");
1927
1928         this._element.addStyleClass("network-item");
1929         if (!this._element.hasStyleClass("network-type-" + this._request.type.name())) {
1930             this._element.removeMatchingStyleClasses("network-type-\\w+");
1931             this._element.addStyleClass("network-type-" + this._request.type.name());
1932         }
1933     },
1934
1935     _refreshNameCell: function()
1936     {
1937         this._nameCell.removeChildren();
1938
1939         if (this._request.type === WebInspector.resourceTypes.Image) {
1940             var previewImage = document.createElement("img");
1941             previewImage.className = "image-network-icon-preview";
1942             this._request.populateImageSource(previewImage);
1943
1944             var iconElement = document.createElement("div");
1945             iconElement.className = "icon";
1946             iconElement.appendChild(previewImage);
1947         } else {
1948             var iconElement = document.createElement("img");
1949             iconElement.className = "icon";
1950         }
1951         this._nameCell.appendChild(iconElement);
1952         this._nameCell.appendChild(document.createTextNode(this._request.name()));
1953         this._appendSubtitle(this._nameCell, this._request.path());
1954         this._nameCell.title = this._request.url;
1955     },
1956
1957     _refreshStatusCell: function()
1958     {
1959         this._statusCell.removeChildren();
1960
1961         if (this._request.failed) {
1962             var failText = this._request.canceled ? WebInspector.UIString("(canceled)") : WebInspector.UIString("(failed)");
1963             if (this._request.localizedFailDescription) {
1964                 this._statusCell.appendChild(document.createTextNode(failText));
1965                 this._appendSubtitle(this._statusCell, this._request.localizedFailDescription);
1966                 this._statusCell.title = failText + " " + this._request.localizedFailDescription;
1967             } else {
1968                 this._statusCell.setTextAndTitle(failText);
1969             }
1970             this._statusCell.addStyleClass("network-dim-cell");
1971             this.element.addStyleClass("network-error-row");
1972             return;
1973         }
1974
1975         this._statusCell.removeStyleClass("network-dim-cell");
1976         this.element.removeStyleClass("network-error-row");
1977
1978         if (this._request.statusCode) {
1979             this._statusCell.appendChild(document.createTextNode(this._request.statusCode));
1980             this._appendSubtitle(this._statusCell, this._request.statusText);
1981             this._statusCell.title = this._request.statusCode + " " + this._request.statusText;
1982             if (this._request.statusCode >= 400)
1983                 this.element.addStyleClass("network-error-row");
1984             if (this._request.cached)
1985                 this._statusCell.addStyleClass("network-dim-cell");
1986         } else {
1987             if (!this._request.isHttpFamily() && this._request.finished)
1988                 this._statusCell.setTextAndTitle(WebInspector.UIString("Success"));
1989             else if (this._request.isPingRequest())
1990                 this._statusCell.setTextAndTitle(WebInspector.UIString("(ping)"));
1991             else
1992                 this._statusCell.setTextAndTitle(WebInspector.UIString("(pending)"));
1993             this._statusCell.addStyleClass("network-dim-cell");
1994         }
1995     },
1996
1997     _refreshTypeCell: function()
1998     {
1999         if (this._request.mimeType) {
2000             this._typeCell.removeStyleClass("network-dim-cell");
2001             this._typeCell.setTextAndTitle(this._request.mimeType);
2002         } else if (this._request.isPingRequest()) {
2003             this._typeCell.removeStyleClass("network-dim-cell");
2004             this._typeCell.setTextAndTitle(this._request.requestContentType());
2005         } else {
2006             this._typeCell.addStyleClass("network-dim-cell");
2007             this._typeCell.setTextAndTitle(WebInspector.UIString("Pending"));
2008         }
2009     },
2010
2011     _refreshInitiatorCell: function()
2012     {
2013         var initiator = this._request.initiator;
2014         if ((initiator && initiator.type !== "other") || this._request.redirectSource) {
2015             this._initiatorCell.removeStyleClass("network-dim-cell");
2016             this._initiatorCell.removeChildren();
2017             if (this._request.redirectSource) {
2018                 var redirectSource = this._request.redirectSource;
2019                 this._initiatorCell.title = redirectSource.url;
2020                 this._initiatorCell.appendChild(WebInspector.linkifyRequestAsNode(redirectSource));
2021                 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Redirect"));
2022             } else if (initiator.type === "script") {
2023                 var topFrame = initiator.stackTrace[0];
2024                 // This could happen when request loading was triggered by console.
2025                 if (!topFrame.url) {
2026                     this._initiatorCell.addStyleClass("network-dim-cell");
2027                     this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other"));
2028                     return;
2029                 }
2030                 this._initiatorCell.title = topFrame.url + ":" + topFrame.lineNumber;
2031                 var urlElement = this._parentView._linkifier.linkifyLocation(topFrame.url, topFrame.lineNumber - 1, 0);
2032                 this._initiatorCell.appendChild(urlElement);
2033                 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Script"));
2034             } else { // initiator.type === "parser"
2035                 this._initiatorCell.title = initiator.url + ":" + initiator.lineNumber;
2036                 this._initiatorCell.appendChild(WebInspector.linkifyResourceAsNode(initiator.url, initiator.lineNumber - 1));
2037                 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Parser"));
2038             }
2039         } else {
2040             this._initiatorCell.addStyleClass("network-dim-cell");
2041             this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other"));
2042         }
2043     },
2044
2045     _refreshSizeCell: function()
2046     {
2047         if (this._request.cached) {
2048             this._sizeCell.setTextAndTitle(WebInspector.UIString("(from cache)"));
2049             this._sizeCell.addStyleClass("network-dim-cell");
2050         } else {
2051             var resourceSize = typeof this._request.resourceSize === "number" ? Number.bytesToString(this._request.resourceSize) : "?";
2052             var transferSize = typeof this._request.transferSize === "number" ? Number.bytesToString(this._request.transferSize) : "?";
2053             this._sizeCell.setTextAndTitle(transferSize);
2054             this._sizeCell.removeStyleClass("network-dim-cell");
2055             this._appendSubtitle(this._sizeCell, resourceSize);
2056         }
2057     },
2058
2059     _refreshTimeCell: function()
2060     {
2061         if (this._request.duration > 0) {
2062             this._timeCell.removeStyleClass("network-dim-cell");
2063             this._timeCell.setTextAndTitle(Number.secondsToString(this._request.duration));
2064             this._appendSubtitle(this._timeCell, Number.secondsToString(this._request.latency));
2065         } else {
2066             this._timeCell.addStyleClass("network-dim-cell");
2067             this._timeCell.setTextAndTitle(WebInspector.UIString("Pending"));
2068         }
2069     },
2070
2071     _appendSubtitle: function(cellElement, subtitleText)
2072     {
2073         var subtitleElement = document.createElement("div");
2074         subtitleElement.className = "network-cell-subtitle";
2075         subtitleElement.textContent = subtitleText;
2076         cellElement.appendChild(subtitleElement);
2077     },
2078
2079     refreshGraph: function(calculator)
2080     {
2081         var percentages = calculator.computeBarGraphPercentages(this._request);
2082         this._percentages = percentages;
2083
2084         this._barAreaElement.removeStyleClass("hidden");
2085
2086         if (!this._graphElement.hasStyleClass("network-type-" + this._request.type.name())) {
2087             this._graphElement.removeMatchingStyleClasses("network-type-\\w+");
2088             this._graphElement.addStyleClass("network-type-" + this._request.type.name());
2089         }
2090
2091         this._barLeftElement.style.setProperty("left", percentages.start + "%");
2092         this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
2093
2094         this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
2095         this._barRightElement.style.setProperty("left", percentages.middle + "%");
2096
2097         var labels = calculator.computeBarGraphLabels(this._request);
2098         this._labelLeftElement.textContent = labels.left;
2099         this._labelRightElement.textContent = labels.right;
2100
2101         var tooltip = (labels.tooltip || "");
2102         this._barLeftElement.title = tooltip;
2103         this._labelLeftElement.title = tooltip;
2104         this._labelRightElement.title = tooltip;
2105         this._barRightElement.title = tooltip;
2106     },
2107
2108     _refreshLabelPositions: function()
2109     {
2110         if (!this._percentages)
2111             return;
2112         this._labelLeftElement.style.removeProperty("left");
2113         this._labelLeftElement.style.removeProperty("right");
2114         this._labelLeftElement.removeStyleClass("before");
2115         this._labelLeftElement.removeStyleClass("hidden");
2116
2117         this._labelRightElement.style.removeProperty("left");
2118         this._labelRightElement.style.removeProperty("right");
2119         this._labelRightElement.removeStyleClass("after");
2120         this._labelRightElement.removeStyleClass("hidden");
2121
2122         const labelPadding = 10;
2123         const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
2124         const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;
2125
2126         if (this._barLeftElement) {
2127             var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
2128             var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
2129         } else {
2130             var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
2131             var rightBarWidth = barRightElementOffsetWidth - labelPadding;
2132         }
2133
2134         const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
2135         const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;
2136
2137         const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
2138         const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
2139         const graphElementOffsetWidth = this._graphElement.offsetWidth;
2140
2141         if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
2142             var leftHidden = true;
2143
2144         if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
2145             var rightHidden = true;
2146
2147         if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
2148             // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
2149             if (labelBefore && !labelAfter)
2150                 leftHidden = true;
2151             else if (labelAfter && !labelBefore)
2152                 rightHidden = true;
2153         }
2154
2155         if (labelBefore) {
2156             if (leftHidden)
2157                 this._labelLeftElement.addStyleClass("hidden");
2158             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
2159             this._labelLeftElement.addStyleClass("before");
2160         } else {
2161             this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
2162             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
2163         }
2164
2165         if (labelAfter) {
2166             if (rightHidden)
2167                 this._labelRightElement.addStyleClass("hidden");
2168             this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
2169             this._labelRightElement.addStyleClass("after");
2170         } else {
2171             this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
2172             this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
2173         }
2174     },
2175
2176     __proto__: WebInspector.DataGridNode.prototype
2177 }
2178
2179
2180 WebInspector.NetworkDataGridNode.NameComparator = function(a, b)
2181 {
2182     var aFileName = a._request.name();
2183     var bFileName = b._request.name();
2184     if (aFileName > bFileName)
2185         return 1;
2186     if (bFileName > aFileName)
2187         return -1;
2188     return 0;
2189 }
2190
2191 WebInspector.NetworkDataGridNode.SizeComparator = function(a, b)
2192 {
2193     if (b._request.cached && !a._request.cached)
2194         return 1;
2195     if (a._request.cached && !b._request.cached)
2196         return -1;
2197
2198     if (a._request.resourceSize === b._request.resourceSize)
2199         return 0;
2200
2201     return a._request.resourceSize - b._request.resourceSize;
2202 }
2203
2204 WebInspector.NetworkDataGridNode.InitiatorComparator = function(a, b)
2205 {
2206     if (!a._request.initiator || a._request.initiator.type === "Other")
2207         return -1;
2208     if (!b._request.initiator || b._request.initiator.type === "Other")
2209         return 1;
2210
2211     if (a._request.initiator.url < b._request.initiator.url)
2212         return -1;
2213     if (a._request.initiator.url > b._request.initiator.url)
2214         return 1;
2215
2216     return a._request.initiator.lineNumber - b._request.initiator.lineNumber;
2217 }
2218
2219 WebInspector.NetworkDataGridNode.RequestPropertyComparator = function(propertyName, revert, a, b)
2220 {
2221     var aValue = a._request[propertyName];
2222     var bValue = b._request[propertyName];
2223     if (aValue > bValue)
2224         return revert ? -1 : 1;
2225     if (bValue > aValue)
2226         return revert ? 1 : -1;
2227     return 0;
2228 }