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