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