OSDN Git Service

Merge WebKit at r73109: Initial merge by git.
[android-x86/external-webkit.git] / 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) 2010 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 WebInspector.NetworkPanel = function()
32 {
33     WebInspector.Panel.call(this, "network");
34
35     this.createSidebar();
36     this.sidebarElement.className = "network-sidebar";
37
38     this._resources = [];
39     this._resourcesById = {};
40     this._resourcesByURL = {};
41     this._lastIdentifier = 0;
42     this._staleResources = [];
43     this._resourceGridNodes = {};
44     this._mainResourceLoadTime = -1;
45     this._mainResourceDOMContentTime = -1;
46     this._hiddenCategories = {};
47
48     this._categories = WebInspector.resourceCategories;
49
50     this.containerElement = document.createElement("div");
51     this.containerElement.id = "network-container";
52     this.sidebarElement.appendChild(this.containerElement);
53
54     this._viewsContainerElement = document.createElement("div");
55     this._viewsContainerElement.id = "network-views";
56     this._viewsContainerElement.className = "hidden";
57     this.element.appendChild(this._viewsContainerElement);
58
59     this._closeButtonElement = document.createElement("button");
60     this._closeButtonElement.id = "network-close-button";
61     this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false);
62     this._viewsContainerElement.appendChild(this._closeButtonElement);
63
64     this._createSortingFunctions();
65     this._createTable();
66     this._createTimelineGrid();
67     this._createStatusbarButtons();
68     this._createFilterStatusBarItems();
69     this._createSummaryBar();
70
71     if (!WebInspector.settings.resourcesLargeRows)
72         this._setLargerResources(WebInspector.settings.resourcesLargeRows);
73
74     this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true);
75     // Enable faster hint.
76     this._popoverHelper.setTimeout(100);
77
78     this.calculator = new WebInspector.NetworkTransferTimeCalculator();
79     this._filter(this._filterAllElement, false);
80
81     this._toggleGridMode();
82 }
83
84 WebInspector.NetworkPanel.prototype = {
85     get toolbarItemLabel()
86     {
87         return WebInspector.UIString("Network");
88     },
89
90     get statusBarItems()
91     {
92         return [this._largerResourcesButton.element, this._preserveLogToggle.element, this._clearButton.element, this._filterBarElement];
93     },
94
95     isCategoryVisible: function(categoryName)
96     {
97         return true;
98     },
99
100     elementsToRestoreScrollPositionsFor: function()
101     {
102         return [this.containerElement];
103     },
104
105     resize: function()
106     {
107         WebInspector.Panel.prototype.resize.call(this);
108         this._dataGrid.updateWidths();
109         this._positionSummaryBar();
110     },
111
112     updateSidebarWidth: function(width)
113     {
114         if (!this._viewingResourceMode)
115             return;
116         WebInspector.Panel.prototype.updateSidebarWidth.call(this, width);
117         if (this._summaryBarElement.parentElement === this.element)
118             this._summaryBarElement.style.width = width + "px";
119     },
120
121     updateMainViewWidth: function(width)
122     {
123         this._viewsContainerElement.style.left = width + "px";
124     },
125
126     handleShortcut: function(event)
127     {
128         if (this._viewingResourceMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
129             this._toggleGridMode();
130             event.handled = true;
131         }
132     },
133
134     _positionSummaryBar: function()
135     {
136         // Position the total bar.
137
138         var fillerRow = this._dataGrid.dataTableBody.lastChild;
139         if (this._summaryBarElement.parentElement !== this.element && fillerRow.offsetHeight > 0) {
140             // Glue status to bottom.
141             if (this._summaryBarRowNode) {
142                 this._dataGrid.removeChild(this._summaryBarRowNode);
143                 delete this._summaryBarRowNode;
144             }
145             this._summaryBarElement.addStyleClass("network-summary-bar-bottom");
146             this._summaryBarElement.style.setProperty("width", this.sidebarElement.offsetWidth + "px");
147             this.element.appendChild(this._summaryBarElement);
148             this._dataGrid.element.style.bottom = "20px";
149             return;
150         }
151
152         if (!this._summaryBarRowNode && !fillerRow.offsetHeight) {
153             // Glue status to table.
154             this._summaryBarRowNode = new WebInspector.NetworkTotalGridNode(this._summaryBarElement);
155             this._summaryBarElement.removeStyleClass("network-summary-bar-bottom");
156             this._summaryBarElement.style.removeProperty("width");
157             this._dataGrid.appendChild(this._summaryBarRowNode);
158             this._dataGrid.element.style.bottom = 0;
159             this._sortItems();
160         }
161     },
162
163     _resetSummaryBar: function()
164     {
165         delete this._summaryBarRowNode;
166         this._summaryBarElement.parentElement.removeChild(this._summaryBarElement);
167         this._updateSummaryBar();
168     },
169
170     _createTimelineGrid: function()
171     {
172         this._timelineGrid = new WebInspector.TimelineGrid();
173         this._timelineGrid.element.addStyleClass("network-timeline-grid");
174         this._dataGrid.element.appendChild(this._timelineGrid.element);
175     },
176
177     _createTable: function()
178     {
179         var columns = {name: {}, method: {}, status: {}, type: {}, size: {}, time: {}, timeline: {}};
180         columns.name.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path"));
181         columns.name.sortable = true;
182         columns.name.width = "20%";
183         columns.name.disclosure = true;
184
185         columns.method.title = WebInspector.UIString("Method");
186         columns.method.sortable = true;
187         columns.method.width = "7%";
188
189         columns.status.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text"));
190         columns.status.sortable = true;
191         columns.status.width = "8%";
192
193         columns.type.title = WebInspector.UIString("Type");
194         columns.type.sortable = true;
195         columns.type.width = "10%";
196
197         columns.size.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Transfer"));
198         columns.size.sortable = true;
199         columns.size.width = "10%";
200         columns.size.aligned = "right";
201
202         columns.time.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Latency"));
203         columns.time.sortable = true;
204         columns.time.width = "10%";
205         columns.time.aligned = "right";
206
207         columns.timeline.title = "";
208         columns.timeline.sortable = false;
209         columns.timeline.width = "37%";
210         columns.timeline.sort = "ascending";
211
212         this._dataGrid = new WebInspector.DataGrid(columns);
213         this.containerElement.appendChild(this._dataGrid.element);
214         this._dataGrid.addEventListener("sorting changed", this._sortItems, this);
215         this._dataGrid.addEventListener("width changed", this._updateDividersIfNeeded, this);
216
217         this._patchTimelineHeader();
218     },
219
220     _makeHeaderFragment: function(title, subtitle)
221     {
222         var fragment = document.createDocumentFragment();
223         fragment.appendChild(document.createTextNode(title));
224         var subtitleDiv = document.createElement("div");
225         subtitleDiv.className = "network-header-subtitle";
226         subtitleDiv.textContent = subtitle;
227         fragment.appendChild(subtitleDiv);
228         return fragment;
229     },
230
231     _patchTimelineHeader: function()
232     {
233         var timelineSorting = document.createElement("select");
234
235         var option = document.createElement("option");
236         option.value = "startTime";
237         option.label = WebInspector.UIString("Timeline");
238         timelineSorting.appendChild(option);
239
240         option = document.createElement("option");
241         option.value = "startTime";
242         option.label = WebInspector.UIString("Start Time");
243         timelineSorting.appendChild(option);
244
245         option = document.createElement("option");
246         option.value = "responseTime";
247         option.label = WebInspector.UIString("Response Time");
248         timelineSorting.appendChild(option);
249
250         option = document.createElement("option");
251         option.value = "endTime";
252         option.label = WebInspector.UIString("End Time");
253         timelineSorting.appendChild(option);
254
255         option = document.createElement("option");
256         option.value = "duration";
257         option.label = WebInspector.UIString("Duration");
258         timelineSorting.appendChild(option);
259
260         option = document.createElement("option");
261         option.value = "latency";
262         option.label = WebInspector.UIString("Latency");
263         timelineSorting.appendChild(option);
264
265         var header = this._dataGrid.headerTableHeader("timeline");
266         header.replaceChild(timelineSorting, header.firstChild);
267
268         timelineSorting.addEventListener("click", function(event) { event.stopPropagation() }, false);
269         timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false);
270         this._timelineSortSelector = timelineSorting;
271     },
272
273     _createSortingFunctions: function()
274     {
275         this._sortingFunctions = {};
276         this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator;
277         this._sortingFunctions.method = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "method", false);
278         this._sortingFunctions.status = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "statusCode", false);
279         this._sortingFunctions.type = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "mimeType", false);
280         this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator;
281         this._sortingFunctions.time = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", false);
282         this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false);
283         this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false);
284         this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "endTime", false);
285         this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "responseReceivedTime", false);
286         this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", true);
287         this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "latency", true);
288
289         var timeCalculator = new WebInspector.NetworkTransferTimeCalculator();
290         var durationCalculator = new WebInspector.NetworkTransferDurationCalculator();
291
292         this._calculators = {};
293         this._calculators.timeline = timeCalculator;
294         this._calculators.startTime = timeCalculator;
295         this._calculators.endTime = timeCalculator;
296         this._calculators.responseTime = timeCalculator;
297         this._calculators.duration = durationCalculator;
298         this._calculators.latency = durationCalculator;
299     },
300
301     _sortItems: function()
302     {
303         var columnIdentifier = this._dataGrid.sortColumnIdentifier;
304         if (columnIdentifier === "timeline") {
305             this._sortByTimeline();
306             return;
307         }
308         var sortingFunction = this._sortingFunctions[columnIdentifier];
309         if (!sortingFunction)
310             return;
311
312         this._dataGrid.sortNodes(sortingFunction, this._dataGrid.sortOrder === "descending");
313         this._timelineSortSelector.selectedIndex = 0;
314     },
315
316     _sortByTimeline: function()
317     {
318         var selectedIndex = this._timelineSortSelector.selectedIndex;
319         if (!selectedIndex)
320             selectedIndex = 1; // Sort by start time by default.
321         var selectedOption = this._timelineSortSelector[selectedIndex];
322         var value = selectedOption.value;
323
324         var sortingFunction = this._sortingFunctions[value];
325         this._dataGrid.sortNodes(sortingFunction);
326         this.calculator = this._calculators[value];
327         if (this.calculator.startAtZero)
328             this._timelineGrid.hideEventDividers();
329         else
330             this._timelineGrid.showEventDividers();
331         this._dataGrid.markColumnAsSortedBy("timeline", "ascending");
332     },
333
334     _createFilterStatusBarItems: function()
335     {
336         var filterBarElement = document.createElement("div");
337         filterBarElement.className = "scope-bar status-bar-item";
338         filterBarElement.id = "network-filter";
339
340         function createFilterElement(category, label)
341         {
342             var categoryElement = document.createElement("li");
343             categoryElement.category = category;
344             categoryElement.className = category;
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 category in this._categories)
360             createFilterElement.call(this, category, this._categories[category].title);
361         this._filterBarElement = filterBarElement;
362     },
363
364     _createSummaryBar: function()
365     {
366         this._summaryBarElement = document.createElement("div");
367         this._summaryBarElement.className = "network-summary-bar";
368         this.containerElement.appendChild(this._summaryBarElement);
369     },
370
371     _updateSummaryBar: function()
372     {
373         this._positionSummaryBar(); // Grid is growing.
374         var numRequests = this._resources.length;
375
376         if (!numRequests) {
377             if (this._summaryBarElement._isDisplayingWarning)
378                 return;
379             this._summaryBarElement._isDisplayingWarning = true;
380
381             var img = document.createElement("img");
382             img.src = "Images/warningIcon.png";
383             this._summaryBarElement.removeChildren();
384             this._summaryBarElement.appendChild(img);
385             this._summaryBarElement.appendChild(document.createTextNode(" "));
386             this._summaryBarElement.appendChild(document.createTextNode(
387                 WebInspector.UIString("No requests captured. Reload the page to see detailed information on the network activity.")));
388             return;
389         }
390         delete this._summaryBarElement._isDisplayingWarning;
391
392         var transferSize = 0;
393         var baseTime = -1;
394         var maxTime = -1;
395         for (var i = 0; i < this._resources.length; ++i) {
396             var resource = this._resources[i];
397             transferSize += (resource.cached || !resource.transferSize) ? 0 : resource.transferSize;
398             if (resource.isMainResource)
399                 baseTime = resource.startTime;
400             if (resource.endTime > maxTime) 
401                 maxTime = resource.endTime;
402         }
403         var text = String.sprintf(WebInspector.UIString("%d requests"), numRequests);
404         text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize));
405         if (baseTime !== -1 && this._mainResourceLoadTime !== -1 && this._mainResourceDOMContentTime !== -1 && this._mainResourceDOMContentTime > baseTime) {
406             text += "  \u2758  " + String.sprintf(WebInspector.UIString("%s (onload: %s, DOMContentLoaded: %s)"),
407                         Number.secondsToString(maxTime - baseTime),
408                         Number.secondsToString(this._mainResourceLoadTime - baseTime),
409                         Number.secondsToString(this._mainResourceDOMContentTime - baseTime));
410         }
411         this._summaryBarElement.textContent = text;
412     },
413
414     _showCategory: function(category)
415     {
416         this._dataGrid.element.addStyleClass("filter-" + category);
417         delete this._hiddenCategories[category];
418     },
419
420     _hideCategory: function(category)
421     {
422         this._dataGrid.element.removeStyleClass("filter-" + category);
423         this._hiddenCategories[category] = true;
424     },
425
426     _updateFilter: function(e)
427     {
428         var isMac = WebInspector.isMac();
429         var selectMultiple = false;
430         if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
431             selectMultiple = true;
432         if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
433             selectMultiple = true;
434
435         this._filter(e.target, selectMultiple);
436         this._positionSummaryBar();
437     },
438
439     _filter: function(target, selectMultiple)
440     {
441         function unselectAll()
442         {
443             for (var i = 0; i < this._filterBarElement.childNodes.length; ++i) {
444                 var child = this._filterBarElement.childNodes[i];
445                 if (!child.category)
446                     continue;
447
448                 child.removeStyleClass("selected");
449                 this._hideCategory(child.category);
450             }
451         }
452
453         if (target.category === this._filterAllElement) {
454             if (target.hasStyleClass("selected")) {
455                 // We can't unselect All, so we break early here
456                 return;
457             }
458
459             // If All wasn't selected, and now is, unselect everything else.
460             unselectAll.call(this);
461         } else {
462             // Something other than All is being selected, so we want to unselect All.
463             if (this._filterAllElement.hasStyleClass("selected")) {
464                 this._filterAllElement.removeStyleClass("selected");
465                 this._hideCategory("all");
466             }
467         }
468
469         if (!selectMultiple) {
470             // If multiple selection is off, we want to unselect everything else
471             // and just select ourselves.
472             unselectAll.call(this);
473
474             target.addStyleClass("selected");
475             this._showCategory(target.category);
476             return;
477         }
478
479         if (target.hasStyleClass("selected")) {
480             // If selectMultiple is turned on, and we were selected, we just
481             // want to unselect ourselves.
482             target.removeStyleClass("selected");
483             this._hideCategory(target.category);
484         } else {
485             // If selectMultiple is turned on, and we weren't selected, we just
486             // want to select ourselves.
487             target.addStyleClass("selected");
488             this._showCategory(target.category);
489         }
490     },
491
492     _scheduleRefresh: function()
493     {
494         if (this._needsRefresh)
495             return;
496
497         this._needsRefresh = true;
498
499         if (this.visible && !("_refreshTimeout" in this))
500             this._refreshTimeout = setTimeout(this.refresh.bind(this), 500);
501     },
502
503     _updateDividersIfNeeded: function(force)
504     {
505         var timelineColumn = this._dataGrid.columns.timeline;
506         for (var i = 0; i < this._dataGrid.resizers.length; ++i) {
507             if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnID) {
508                 // Position timline grid location.
509                 this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left;
510                 this._timelineGrid.element.style.right = "18px";
511             }
512         }
513
514         var proceed = true;
515         if (!this.visible) {
516             this._scheduleRefresh();
517             proceed = false;
518         } else
519             proceed = this._timelineGrid.updateDividers(force, this.calculator);
520         
521         if (!proceed)
522             return;
523
524         if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) {
525             // If our current sorting method starts at zero, that means it shows all
526             // resources starting at the same point, and so onLoad event and DOMContent
527             // event lines really wouldn't make much sense here, so don't render them.
528             // Additionally, if the calculator doesn't have the computePercentageFromEventTime
529             // function defined, we are probably sorting by size, and event times aren't relevant
530             // in this case.
531             return;
532         }
533
534         this._timelineGrid.removeEventDividers();
535         if (this._mainResourceLoadTime !== -1) {
536             var percent = this.calculator.computePercentageFromEventTime(this._mainResourceLoadTime);
537
538             var loadDivider = document.createElement("div");
539             loadDivider.className = "network-event-divider network-red-divider";
540
541             var loadDividerPadding = document.createElement("div");
542             loadDividerPadding.className = "network-event-divider-padding";
543             loadDividerPadding.title = WebInspector.UIString("Load event fired");
544             loadDividerPadding.appendChild(loadDivider);
545             loadDividerPadding.style.left = percent + "%";
546             this._timelineGrid.addEventDivider(loadDividerPadding);
547         }
548         
549         if (this._mainResourceDOMContentTime !== -1) {
550             var percent = this.calculator.computePercentageFromEventTime(this._mainResourceDOMContentTime);
551
552             var domContentDivider = document.createElement("div");
553             domContentDivider.className = "network-event-divider network-blue-divider";
554             
555             var domContentDividerPadding = document.createElement("div");
556             domContentDividerPadding.className = "network-event-divider-padding";
557             domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired");
558             domContentDividerPadding.appendChild(domContentDivider);
559             domContentDividerPadding.style.left = percent + "%";
560             this._timelineGrid.addEventDivider(domContentDividerPadding);
561         }
562     },
563
564     _refreshIfNeeded: function()
565     {
566         if (this._needsRefresh)
567             this.refresh();
568     },
569
570     _invalidateAllItems: function()
571     {
572         this._staleResources = this._resources.slice();
573     },
574
575     get calculator()
576     {
577         return this._calculator;
578     },
579
580     set calculator(x)
581     {
582         if (!x || this._calculator === x)
583             return;
584
585         this._calculator = x;
586         this._calculator.reset();
587
588         this._invalidateAllItems();
589         this.refresh();
590     },
591
592     _resourceGridNode: function(resource)
593     {
594         return this._resourceGridNodes[resource.identifier];
595     },
596
597     revealAndSelectItem: function(resource)
598     {
599         var node = this._resourceGridNode(resource);
600         if (node) {
601             node.reveal();
602             node.select(true);
603         }
604     },
605
606     addEventDivider: function(divider)
607     {
608         this._timelineGrid.addEventDivider(divider);
609     },
610
611     _createStatusbarButtons: function()
612     {
613         this._preserveLogToggle = new WebInspector.StatusBarButton(WebInspector.UIString("Preserve Log upon Navigation"), "record-profile-status-bar-item");
614         this._preserveLogToggle.addEventListener("click", this._onPreserveLogClicked.bind(this), false);
615
616         this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
617         this._clearButton.addEventListener("click", this._reset.bind(this), false);
618
619         this._largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item");
620         this._largerResourcesButton.toggled = WebInspector.settings.resourcesLargeRows;
621         this._largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false);
622     },
623
624     set mainResourceLoadTime(x)
625     {
626         if (this._mainResourceLoadTime === x)
627             return;
628         
629         this._mainResourceLoadTime = x || -1;
630         // Update the dividers to draw the new line
631         this._updateDividersIfNeeded(true);
632     },
633
634     set mainResourceDOMContentTime(x)
635     {
636         if (this._mainResourceDOMContentTime === x)
637             return;
638
639         this._mainResourceDOMContentTime = x || -1;
640         this._updateDividersIfNeeded(true);
641     },
642
643     show: function()
644     {
645         WebInspector.Panel.prototype.show.call(this);
646         this._refreshIfNeeded();
647
648         if (this.visibleView)
649             this.visibleView.show(this._viewsContainerElement);
650
651         this._dataGrid.updateWidths();
652         this._positionSummaryBar();
653     },
654
655     hide: function()
656     {
657         WebInspector.Panel.prototype.hide.call(this);
658         this._popoverHelper.hidePopup();
659     },
660
661     get searchableViews()
662     {
663         var views = [];
664         return views;
665     },
666
667     searchMatchFound: function(view, matches)
668     {
669         this._resourceGridNode(view.resource).searchMatches = matches;
670     },
671
672     searchCanceled: function(startingNewSearch)
673     {
674         WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
675
676         if (startingNewSearch || !this._resources)
677             return;
678     },
679
680     performSearch: function(query)
681     {
682         WebInspector.Panel.prototype.performSearch.call(this, query);
683     },
684
685     refresh: function()
686     {
687         this._needsRefresh = false;
688         if ("_refreshTimeout" in this) {
689             clearTimeout(this._refreshTimeout);
690             delete this._refreshTimeout;
691         }
692
693         var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow();
694         var staleItemsLength = this._staleResources.length;
695         var boundariesChanged = false;
696
697         for (var i = 0; i < staleItemsLength; ++i) {
698             var resource = this._staleResources[i];
699             var node = this._resourceGridNode(resource);
700             if (!node) {
701                 // Create the timeline tree element and graph.
702                 node = new WebInspector.NetworkDataGridNode(this, resource);
703                 this._resourceGridNodes[resource.identifier] = node;
704                 this._dataGrid.appendChild(node);
705             }
706             node.refreshResource();
707
708             if (this.calculator.updateBoundaries(resource))
709                 boundariesChanged = true;
710         }
711
712         if (boundariesChanged) {
713             // The boundaries changed, so all item graphs are stale.
714             this._invalidateAllItems();
715             staleItemsLength = this._staleResources.length;
716         }
717
718         for (var i = 0; i < staleItemsLength; ++i)
719             this._resourceGridNode(this._staleResources[i]).refreshGraph(this.calculator);
720
721         this._staleResources = [];
722         this._sortItems();
723         this._updateSummaryBar();
724         this._dataGrid.updateWidths();
725
726         if (wasScrolledToLastRow)
727             this._dataGrid.scrollToLastRow();
728     },
729
730     _onPreserveLogClicked: function(e)
731     {
732         this._preserveLogToggle.toggled = !this._preserveLogToggle.toggled;
733     },
734
735     reset: function()
736     {
737         if (!this._preserveLogToggle.toggled)
738             this._reset();
739     },
740
741     _reset: function()
742     {
743         this._popoverHelper.hidePopup();
744         this._closeVisibleResource();
745
746         this._toggleGridMode();
747
748         // Begin reset timeline
749         if (this._calculator)
750             this._calculator.reset();
751
752         this._resources = [];
753         this._resourcesById = {};
754         this._resourcesByURL = {};
755         this._staleResources = [];
756         this._resourceGridNodes = {};
757
758         this._dataGrid.removeChildren();
759         delete this._summaryBarRowNode;
760         this._updateDividersIfNeeded(true);
761         // End reset timeline.
762
763         this._mainResourceLoadTime = -1;
764         this._mainResourceDOMContentTime = -1;
765  
766         this._viewsContainerElement.removeChildren();
767         this._viewsContainerElement.appendChild(this._closeButtonElement);
768         this._resetSummaryBar();
769     },
770
771     get resources()
772     {
773         return this._resourcesById;
774     },
775
776     refreshResource: function(resource)
777     {
778         if (!resource.identifier)
779             resource.identifier = "network:" + this._lastIdentifier++;
780
781         if (!this._resourcesById[resource.identifier]) {
782             this._resources.push(resource);
783             this._resourcesById[resource.identifier] = resource;
784             this._resourcesByURL[resource.url] = resource;
785
786             // Pull all the redirects of the main resource upon commit load.
787             if (resource.redirects) {
788                 for (var i = 0; i < resource.redirects.length; ++i)
789                     this.refreshResource(resource.redirects[i]);
790             }
791         }
792
793         this._staleResources.push(resource);
794         this._scheduleRefresh();
795         
796         if (!resource)
797             return;
798
799         var oldView = WebInspector.ResourceManager.existingResourceViewForResource(resource);
800         if (!oldView)
801             return;
802
803         if (WebInspector.ResourceManager.resourceViewTypeMatchesResource(resource))
804             return;
805
806         var newView = WebInspector.ResourceManager.recreateResourceView(resource);
807         if (this.visibleView === oldView)
808             this.visibleView = newView;
809     },
810
811     canShowSourceLine: function(url, line)
812     {
813         return !!this._resourcesByURL[url];
814     },
815
816     showSourceLine: function(url, line)
817     {
818         this._showResource(this._resourcesByURL[url], line);
819     },
820
821     _showResource: function(resource, line)
822     {
823         if (!resource)
824             return;
825
826         this._popoverHelper.hidePopup();
827
828         this._toggleViewingResourceMode();
829
830         if (this.visibleView) {
831             this.visibleView.detach();
832             delete this.visibleView;
833         }
834
835         var view = new WebInspector.NetworkItemView(resource);
836         view.show(this._viewsContainerElement);
837         this.visibleView = view;
838
839         this.updateSidebarWidth();
840     },
841
842     _closeVisibleResource: function()
843     {
844         this.element.removeStyleClass("viewing-resource");
845
846         if (this.visibleView) {
847             this.visibleView.detach();
848             delete this.visibleView;
849         }
850
851         if (this._lastSelectedGraphTreeElement)
852             this._lastSelectedGraphTreeElement.select(true);
853
854         this.updateSidebarWidth();
855     },
856
857     _toggleLargerResources: function()
858     {
859         WebInspector.settings.resourcesLargeRows = !WebInspector.settings.resourcesLargeRows;
860         this._setLargerResources(WebInspector.settings.resourcesLargeRows);
861     },
862
863     _setLargerResources: function(enabled)
864     {
865         this._largerResourcesButton.toggled = enabled;
866         if (!enabled) {
867             this._largerResourcesButton.title = WebInspector.UIString("Use large resource rows.");
868             this._dataGrid.element.addStyleClass("small");
869             this._timelineGrid.element.addStyleClass("small");
870             this._viewsContainerElement.addStyleClass("small");
871         } else {
872             this._largerResourcesButton.title = WebInspector.UIString("Use small resource rows.");
873             this._dataGrid.element.removeStyleClass("small");
874             this._timelineGrid.element.removeStyleClass("small");
875             this._viewsContainerElement.removeStyleClass("small");
876         }
877         this._positionSummaryBar();
878     },
879
880     _getPopoverAnchor: function(element)
881     {
882         var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label");
883         if (!anchor)
884             return null;
885         var resource = anchor.parentElement.resource;
886         return resource && resource.timing ? anchor : null;
887     },
888
889     _showPopover: function(anchor)
890     {
891         var resource = anchor.parentElement.resource;
892         var tableElement = WebInspector.ResourceTimingView.createTimingTable(resource);
893         var popover = new WebInspector.Popover(tableElement);
894         popover.show(anchor);
895         return popover;
896     },
897
898     _toggleGridMode: function()
899     {
900         if (this._viewingResourceMode) {
901             this._viewingResourceMode = false;
902             this.element.removeStyleClass("viewing-resource");
903             this._dataGrid.element.removeStyleClass("viewing-resource-mode");
904             this._viewsContainerElement.addStyleClass("hidden");
905             this.sidebarElement.style.right = 0;
906             this.sidebarElement.style.removeProperty("width");
907             this._summaryBarElement.style.removeProperty("width");
908             if (this._dataGrid.selectedNode)
909                 this._dataGrid.selectedNode.selected = false;
910         }
911
912         if (this._briefGrid) {
913             this._dataGrid.element.removeStyleClass("full-grid-mode");
914             this._dataGrid.element.addStyleClass("brief-grid-mode");
915
916             this._dataGrid.hideColumn("method");
917             this._dataGrid.hideColumn("status");
918             this._dataGrid.hideColumn("type");
919             this._dataGrid.hideColumn("size");
920             this._dataGrid.hideColumn("time");
921
922             var widths = {};
923             widths.name = 20;
924             widths.timeline = 80;
925         } else {
926             this._dataGrid.element.addStyleClass("full-grid-mode");
927             this._dataGrid.element.removeStyleClass("brief-grid-mode");
928
929             this._dataGrid.showColumn("method");
930             this._dataGrid.showColumn("status");
931             this._dataGrid.showColumn("type");
932             this._dataGrid.showColumn("size");
933             this._dataGrid.showColumn("time");
934
935             var widths = {};
936             widths.name = 20;
937             widths.method = 7;
938             widths.status = 8;
939             widths.type = 10;
940             widths.size = 10;
941             widths.time = 10;
942             widths.timeline = 37;
943         }
944
945         this._dataGrid.showColumn("timeline");
946         this._dataGrid.applyColumnWidthsMap(widths);
947
948     },
949
950     _toggleViewingResourceMode: function()
951     {
952         if (this._viewingResourceMode)
953             return;
954         this._viewingResourceMode = true;
955         this._preservedColumnWidths = this._dataGrid.columnWidthsMap();
956
957         this.element.addStyleClass("viewing-resource");
958         this._dataGrid.element.addStyleClass("viewing-resource-mode");
959         this._dataGrid.element.removeStyleClass("full-grid-mode");
960         this._dataGrid.element.removeStyleClass("brief-grid-mode");
961
962         this._dataGrid.hideColumn("method");
963         this._dataGrid.hideColumn("status");
964         this._dataGrid.hideColumn("type");
965         this._dataGrid.hideColumn("size");
966         this._dataGrid.hideColumn("time");
967         this._dataGrid.hideColumn("timeline");
968
969         this._viewsContainerElement.removeStyleClass("hidden");
970         this.updateSidebarWidth(200);
971
972         var widths = {};
973         widths.name = 100;
974         this._dataGrid.applyColumnWidthsMap(widths);
975     }
976 }
977
978 WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype;
979
980 WebInspector.NetworkBaseCalculator = function()
981 {
982 }
983
984 WebInspector.NetworkBaseCalculator.prototype = {
985     computeSummaryValues: function(items)
986     {
987         var total = 0;
988         var categoryValues = {};
989
990         var itemsLength = items.length;
991         for (var i = 0; i < itemsLength; ++i) {
992             var item = items[i];
993             var value = this._value(item);
994             if (typeof value === "undefined")
995                 continue;
996             if (!(item.category.name in categoryValues))
997                 categoryValues[item.category.name] = 0;
998             categoryValues[item.category.name] += value;
999             total += value;
1000         }
1001
1002         return {categoryValues: categoryValues, total: total};
1003     },
1004
1005     computeBarGraphPercentages: function(item)
1006     {
1007         return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100};
1008     },
1009
1010     computeBarGraphLabels: function(item)
1011     {
1012         const label = this.formatValue(this._value(item));
1013         return {left: label, right: label, tooltip: label};
1014     },
1015
1016     get boundarySpan()
1017     {
1018         return this.maximumBoundary - this.minimumBoundary;
1019     },
1020
1021     updateBoundaries: function(item)
1022     {
1023         this.minimumBoundary = 0;
1024
1025         var value = this._value(item);
1026         if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
1027             this.maximumBoundary = value;
1028             return true;
1029         }
1030         return false;
1031     },
1032
1033     reset: function()
1034     {
1035         delete this.minimumBoundary;
1036         delete this.maximumBoundary;
1037     },
1038
1039     _value: function(item)
1040     {
1041         return 0;
1042     },
1043
1044     formatValue: function(value)
1045     {
1046         return value.toString();
1047     }
1048 }
1049
1050 WebInspector.NetworkTimeCalculator = function(startAtZero)
1051 {
1052     WebInspector.NetworkBaseCalculator.call(this);
1053     this.startAtZero = startAtZero;
1054 }
1055
1056 WebInspector.NetworkTimeCalculator.prototype = {
1057     computeSummaryValues: function(resources)
1058     {
1059         var resourcesByCategory = {};
1060         var resourcesLength = resources.length;
1061         for (var i = 0; i < resourcesLength; ++i) {
1062             var resource = resources[i];
1063             if (!(resource.category.name in resourcesByCategory))
1064                 resourcesByCategory[resource.category.name] = [];
1065             resourcesByCategory[resource.category.name].push(resource);
1066         }
1067
1068         var earliestStart;
1069         var latestEnd;
1070         var categoryValues = {};
1071         for (var category in resourcesByCategory) {
1072             resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime);
1073             categoryValues[category] = 0;
1074
1075             var segment = {start: -1, end: -1};
1076
1077             var categoryResources = resourcesByCategory[category];
1078             var resourcesLength = categoryResources.length;
1079             for (var i = 0; i < resourcesLength; ++i) {
1080                 var resource = categoryResources[i];
1081                 if (resource.startTime === -1 || resource.endTime === -1)
1082                     continue;
1083
1084                 if (typeof earliestStart === "undefined")
1085                     earliestStart = resource.startTime;
1086                 else
1087                     earliestStart = Math.min(earliestStart, resource.startTime);
1088
1089                 if (typeof latestEnd === "undefined")
1090                     latestEnd = resource.endTime;
1091                 else
1092                     latestEnd = Math.max(latestEnd, resource.endTime);
1093
1094                 if (resource.startTime <= segment.end) {
1095                     segment.end = Math.max(segment.end, resource.endTime);
1096                     continue;
1097                 }
1098
1099                 categoryValues[category] += segment.end - segment.start;
1100
1101                 segment.start = resource.startTime;
1102                 segment.end = resource.endTime;
1103             }
1104
1105             // Add the last segment
1106             categoryValues[category] += segment.end - segment.start;
1107         }
1108
1109         return {categoryValues: categoryValues, total: latestEnd - earliestStart};
1110     },
1111
1112     computeBarGraphPercentages: function(resource)
1113     {
1114         if (resource.startTime !== -1)
1115             var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100;
1116         else
1117             var start = 0;
1118
1119         if (resource.responseReceivedTime !== -1)
1120             var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100;
1121         else
1122             var middle = (this.startAtZero ? start : 100);
1123
1124         if (resource.endTime !== -1)
1125             var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100;
1126         else
1127             var end = (this.startAtZero ? middle : 100);
1128
1129         if (this.startAtZero) {
1130             end -= start;
1131             middle -= start;
1132             start = 0;
1133         }
1134
1135         return {start: start, middle: middle, end: end};
1136     },
1137     
1138     computePercentageFromEventTime: function(eventTime)
1139     {
1140         // This function computes a percentage in terms of the total loading time
1141         // of a specific event. If startAtZero is set, then this is useless, and we
1142         // want to return 0.
1143         if (eventTime !== -1 && !this.startAtZero)
1144             return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100;
1145
1146         return 0;
1147     },
1148
1149     computeBarGraphLabels: function(resource)
1150     {
1151         var rightLabel = "";
1152         if (resource.responseReceivedTime !== -1 && resource.endTime !== -1)
1153             rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime);
1154
1155         var hasLatency = resource.latency > 0;
1156         if (hasLatency)
1157             var leftLabel = this.formatValue(resource.latency);
1158         else
1159             var leftLabel = rightLabel;
1160
1161         if (resource.timing)
1162             return {left: leftLabel, right: rightLabel};
1163
1164         if (hasLatency && rightLabel) {
1165             var total = this.formatValue(resource.duration);
1166             var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total);
1167         } else if (hasLatency)
1168             var tooltip = WebInspector.UIString("%s latency", leftLabel);
1169         else if (rightLabel)
1170             var tooltip = WebInspector.UIString("%s download", rightLabel);
1171
1172         if (resource.cached)
1173             tooltip = WebInspector.UIString("%s (from cache)", tooltip);
1174         return {left: leftLabel, right: rightLabel, tooltip: tooltip};
1175     },
1176
1177     updateBoundaries: function(resource)
1178     {
1179         var didChange = false;
1180
1181         var lowerBound;
1182         if (this.startAtZero)
1183             lowerBound = 0;
1184         else
1185             lowerBound = this._lowerBound(resource);
1186
1187         if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) {
1188             this.minimumBoundary = lowerBound;
1189             didChange = true;
1190         }
1191
1192         var upperBound = this._upperBound(resource);
1193         if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) {
1194             this.maximumBoundary = upperBound;
1195             didChange = true;
1196         }
1197
1198         return didChange;
1199     },
1200
1201     formatValue: function(value)
1202     {
1203         return Number.secondsToString(value, WebInspector.UIString);
1204     },
1205
1206     _lowerBound: function(resource)
1207     {
1208         return 0;
1209     },
1210
1211     _upperBound: function(resource)
1212     {
1213         return 0;
1214     }
1215 }
1216
1217 WebInspector.NetworkTimeCalculator.prototype.__proto__ = WebInspector.NetworkBaseCalculator.prototype;
1218
1219 WebInspector.NetworkTransferTimeCalculator = function()
1220 {
1221     WebInspector.NetworkTimeCalculator.call(this, false);
1222 }
1223
1224 WebInspector.NetworkTransferTimeCalculator.prototype = {
1225     formatValue: function(value)
1226     {
1227         return Number.secondsToString(value, WebInspector.UIString);
1228     },
1229
1230     _lowerBound: function(resource)
1231     {
1232         return resource.startTime;
1233     },
1234
1235     _upperBound: function(resource)
1236     {
1237         return resource.endTime;
1238     }
1239 }
1240
1241 WebInspector.NetworkTransferTimeCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
1242
1243 WebInspector.NetworkTransferDurationCalculator = function()
1244 {
1245     WebInspector.NetworkTimeCalculator.call(this, true);
1246 }
1247
1248 WebInspector.NetworkTransferDurationCalculator.prototype = {
1249     formatValue: function(value)
1250     {
1251         return Number.secondsToString(value, WebInspector.UIString);
1252     },
1253
1254     _upperBound: function(resource)
1255     {
1256         return resource.duration;
1257     }
1258 }
1259
1260 WebInspector.NetworkTransferDurationCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype;
1261
1262 WebInspector.NetworkDataGridNode = function(panel, resource)
1263 {
1264     WebInspector.DataGridNode.call(this, {});
1265     this._panel = panel;
1266     this._resource = resource;
1267 }
1268
1269 WebInspector.NetworkDataGridNode.prototype = {
1270     createCells: function()
1271     {
1272         this._nameCell = this._createDivInTD("name");
1273         this._methodCell = this._createDivInTD("method");
1274         this._statusCell = this._createDivInTD("status");
1275         this._typeCell = this._createDivInTD("type");
1276         this._sizeCell = this._createDivInTD("size");
1277         this._timeCell = this._createDivInTD("time");
1278         this._createTimelineCell();
1279         this._nameCell.addEventListener("click", this.select.bind(this), false);
1280     },
1281
1282     select: function()
1283     {
1284         this._panel._showResource(this._resource);
1285         WebInspector.DataGridNode.prototype.select.apply(this, arguments);
1286     },
1287
1288     get selectable()
1289     {
1290         if (!this._panel._viewingResourceMode)
1291             return false;
1292         if (!this._panel._hiddenCategories.all)
1293             return true;
1294         if (this._panel._hiddenCategories[this._resource.category.name])
1295             return false;
1296         return true;
1297     },
1298
1299     _createDivInTD: function(columnIdentifier)
1300     {
1301         var td = document.createElement("td");
1302         td.className = columnIdentifier + "-column";
1303         var div = document.createElement("div");
1304         td.appendChild(div);
1305         this._element.appendChild(td);
1306         return div;
1307     },
1308
1309     _createTimelineCell: function()
1310     {
1311         this._graphElement = document.createElement("div");
1312         this._graphElement.className = "network-graph-side";
1313
1314         this._barAreaElement = document.createElement("div");
1315         //    this._barAreaElement.className = "network-graph-bar-area hidden";
1316         this._barAreaElement.className = "network-graph-bar-area";
1317         this._barAreaElement.resource = this._resource;
1318         this._graphElement.appendChild(this._barAreaElement);
1319
1320         this._barLeftElement = document.createElement("div");
1321         this._barLeftElement.className = "network-graph-bar waiting";
1322         this._barAreaElement.appendChild(this._barLeftElement);
1323
1324         this._barRightElement = document.createElement("div");
1325         this._barRightElement.className = "network-graph-bar";
1326         this._barAreaElement.appendChild(this._barRightElement);
1327
1328
1329         this._labelLeftElement = document.createElement("div");
1330         this._labelLeftElement.className = "network-graph-label waiting";
1331         this._barAreaElement.appendChild(this._labelLeftElement);
1332
1333         this._labelRightElement = document.createElement("div");
1334         this._labelRightElement.className = "network-graph-label";
1335         this._barAreaElement.appendChild(this._labelRightElement);
1336
1337         this._graphElement.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false);
1338
1339         this._timelineCell = document.createElement("td");
1340         this._timelineCell.className = "timeline-column";
1341         this._element.appendChild(this._timelineCell);
1342         this._timelineCell.appendChild(this._graphElement);
1343     },
1344
1345     refreshResource: function()
1346     {
1347         this._refreshNameCell();
1348
1349         this._methodCell.textContent = this._resource.requestMethod;
1350
1351         this._refreshStatusCell();
1352
1353         if (this._resource.mimeType) {
1354             this._typeCell.removeStyleClass("network-dim-cell");
1355             this._typeCell.textContent = this._resource.mimeType;
1356         } else {
1357             this._typeCell.addStyleClass("network-dim-cell");
1358             this._typeCell.textContent = WebInspector.UIString("Pending");
1359         }
1360
1361         this._refreshSizeCell();
1362         this._refreshTimeCell();
1363
1364         if (this._resource.cached)
1365             this._graphElement.addStyleClass("resource-cached");
1366
1367         if (!this._element.hasStyleClass("network-category-" + this._resource.category.name)) {
1368             this._element.removeMatchingStyleClasses("network-category-\\w+");
1369             this._element.addStyleClass("network-category-" + this._resource.category.name);
1370         }
1371     },
1372
1373     _refreshNameCell: function()
1374     {
1375         this._nameCell.removeChildren();
1376
1377         if (this._resource.category === WebInspector.resourceCategories.images) {
1378             var previewImage = document.createElement("img");
1379             previewImage.className = "image-network-icon-preview";
1380
1381             function onResourceContent()
1382             {
1383                 previewImage.src = this._resource.contentURL;
1384             }
1385             if (Preferences.useDataURLForResourceImageIcons)
1386                 this._resource.requestContent(onResourceContent.bind(this));
1387             else
1388                 previewImage.src = this._resource.url;
1389
1390             var iconElement = document.createElement("div");
1391             iconElement.className = "icon";
1392             iconElement.appendChild(previewImage);
1393         } else {
1394             var iconElement = document.createElement("img");
1395             iconElement.className = "icon";
1396         }
1397         this._nameCell.appendChild(iconElement);
1398         this._nameCell.appendChild(document.createTextNode(this._fileName()));
1399
1400
1401         var subtitle = this._resource.displayDomain;
1402
1403         if (this._resource.path && this._resource.lastPathComponent) {
1404             var lastPathComponentIndex = this._resource.path.lastIndexOf("/" + this._resource.lastPathComponent);
1405             if (lastPathComponentIndex != -1)
1406                 subtitle += this._resource.path.substring(0, lastPathComponentIndex);
1407         }
1408
1409         this._appendSubtitle(this._nameCell, subtitle);
1410         this._nameCell.title = this._resource.url;
1411     },
1412
1413     _fileName: function()
1414     {
1415         var fileName = this._resource.displayName;
1416         if (this._resource.queryString)
1417             fileName += "?" + this._resource.queryString;
1418         return fileName;
1419     },
1420
1421     _refreshStatusCell: function()
1422     {
1423         this._statusCell.removeChildren();
1424
1425         var fromCache = this._resource.cached;
1426         if (fromCache) {
1427             this._statusCell.textContent = WebInspector.UIString("(from cache)");
1428             this._statusCell.addStyleClass("network-dim-cell");
1429             return;
1430         }
1431
1432         this._statusCell.removeStyleClass("network-dim-cell");
1433         if (this._resource.statusCode) {
1434             this._statusCell.appendChild(document.createTextNode(this._resource.statusCode));
1435             this._statusCell.removeStyleClass("network-dim-cell");
1436             this._appendSubtitle(this._statusCell, this._resource.statusText);
1437             this._statusCell.title = this._resource.statusCode + " " + this._resource.statusText;
1438         } else {
1439             this._statusCell.addStyleClass("network-dim-cell");
1440             this._statusCell.textContent = WebInspector.UIString("Pending");
1441         }
1442     },
1443
1444     _refreshSizeCell: function()
1445     {
1446         var resourceSize = typeof this._resource.resourceSize === "number" ? Number.bytesToString(this._resource.resourceSize) : "?";
1447         var transferSize = typeof this._resource.transferSize === "number" ? Number.bytesToString(this._resource.transferSize) : "?";
1448         var fromCache = this._resource.cached;
1449         this._sizeCell.textContent = !fromCache ? resourceSize : WebInspector.UIString("(from cache)");
1450         if (fromCache)
1451             this._sizeCell.addStyleClass("network-dim-cell");
1452         else
1453             this._sizeCell.removeStyleClass("network-dim-cell");
1454         if (!fromCache)
1455             this._appendSubtitle(this._sizeCell, transferSize);
1456     },
1457
1458     _refreshTimeCell: function()
1459     {
1460         if (this._resource.duration > 0) {
1461             this._timeCell.removeStyleClass("network-dim-cell");
1462             this._timeCell.textContent = Number.secondsToString(this._resource.duration);
1463             this._appendSubtitle(this._timeCell, Number.secondsToString(this._resource.latency));
1464         } else {
1465             this._timeCell.addStyleClass("network-dim-cell");
1466             this._timeCell.textContent = WebInspector.UIString("Pending");
1467         }
1468     },
1469
1470     _appendSubtitle: function(cellElement, subtitleText)
1471     {
1472         var subtitleElement = document.createElement("div");
1473         subtitleElement.className = "network-cell-subtitle";
1474         subtitleElement.textContent = subtitleText;
1475         cellElement.appendChild(subtitleElement);
1476     },
1477
1478     refreshGraph: function(calculator)
1479     {
1480         var percentages = calculator.computeBarGraphPercentages(this._resource);
1481         this._percentages = percentages;
1482
1483         this._barAreaElement.removeStyleClass("hidden");
1484
1485         if (!this._graphElement.hasStyleClass("network-category-" + this._resource.category.name)) {
1486             this._graphElement.removeMatchingStyleClasses("network-category-\\w+");
1487             this._graphElement.addStyleClass("network-category-" + this._resource.category.name);
1488         }
1489
1490         this._barLeftElement.style.setProperty("left", percentages.start + "%");
1491         this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%");
1492
1493         this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%");
1494         this._barRightElement.style.setProperty("left", percentages.middle + "%");
1495
1496         var labels = calculator.computeBarGraphLabels(this._resource);
1497         this._labelLeftElement.textContent = labels.left;
1498         this._labelRightElement.textContent = labels.right;
1499
1500         var tooltip = (labels.tooltip || "");
1501         this._barLeftElement.title = tooltip;
1502         this._labelLeftElement.title = tooltip;
1503         this._labelRightElement.title = tooltip;
1504         this._barRightElement.title = tooltip;
1505     },
1506
1507     _refreshLabelPositions: function()
1508     {
1509         if (!this._percentages)
1510             return;
1511         this._labelLeftElement.style.removeProperty("left");
1512         this._labelLeftElement.style.removeProperty("right");
1513         this._labelLeftElement.removeStyleClass("before");
1514         this._labelLeftElement.removeStyleClass("hidden");
1515
1516         this._labelRightElement.style.removeProperty("left");
1517         this._labelRightElement.style.removeProperty("right");
1518         this._labelRightElement.removeStyleClass("after");
1519         this._labelRightElement.removeStyleClass("hidden");
1520
1521         const labelPadding = 10;
1522         const barRightElementOffsetWidth = this._barRightElement.offsetWidth;
1523         const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth;
1524
1525         if (this._barLeftElement) {
1526             var leftBarWidth = barLeftElementOffsetWidth - labelPadding;
1527             var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding;
1528         } else {
1529             var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding;
1530             var rightBarWidth = barRightElementOffsetWidth - labelPadding;
1531         }
1532
1533         const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth;
1534         const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth;
1535
1536         const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth);
1537         const labelAfter = (labelRightElementOffsetWidth > rightBarWidth);
1538         const graphElementOffsetWidth = this._graphElement.offsetWidth;
1539
1540         if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10))
1541             var leftHidden = true;
1542
1543         if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10))
1544             var rightHidden = true;
1545
1546         if (barLeftElementOffsetWidth == barRightElementOffsetWidth) {
1547             // The left/right label data are the same, so a before/after label can be replaced by an on-bar label.
1548             if (labelBefore && !labelAfter)
1549                 leftHidden = true;
1550             else if (labelAfter && !labelBefore)
1551                 rightHidden = true;
1552         }
1553
1554         if (labelBefore) {
1555             if (leftHidden)
1556                 this._labelLeftElement.addStyleClass("hidden");
1557             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%");
1558             this._labelLeftElement.addStyleClass("before");
1559         } else {
1560             this._labelLeftElement.style.setProperty("left", this._percentages.start + "%");
1561             this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%");
1562         }
1563
1564         if (labelAfter) {
1565             if (rightHidden)
1566                 this._labelRightElement.addStyleClass("hidden");
1567             this._labelRightElement.style.setProperty("left", this._percentages.end + "%");
1568             this._labelRightElement.addStyleClass("after");
1569         } else {
1570             this._labelRightElement.style.setProperty("left", this._percentages.middle + "%");
1571             this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%");
1572         }
1573     }
1574 }
1575
1576 WebInspector.NetworkDataGridNode.NameComparator = function(a, b)
1577 {
1578     var aFileName = a._resource.displayName + (a._resource.queryString ? a._resource.queryString : "");
1579     var bFileName = b._resource.displayName + (b._resource.queryString ? b._resource.queryString : "");
1580     if (aFileName > bFileName)
1581         return 1;
1582     if (bFileName > aFileName)
1583         return -1;
1584     return 0;
1585 }
1586
1587 WebInspector.NetworkDataGridNode.SizeComparator = function(a, b)
1588 {
1589     if (b._resource.cached && !a._resource.cached)
1590         return 1;
1591     if (a._resource.cached && !b._resource.cached)
1592         return -1;
1593
1594     if (a._resource.resourceSize === b._resource.resourceSize)
1595         return 0;
1596
1597     return a._resource.resourceSize - b._resource.resourceSize;
1598 }
1599
1600 WebInspector.NetworkDataGridNode.ResourcePropertyComparator = function(propertyName, revert, a, b)
1601 {
1602     var aValue = a._resource[propertyName];
1603     var bValue = b._resource[propertyName];
1604     if (aValue > bValue)
1605         return revert ? -1 : 1;
1606     if (bValue > aValue)
1607         return revert ? 1 : -1;
1608     return 0;
1609 }
1610
1611 WebInspector.NetworkDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
1612
1613 WebInspector.NetworkTotalGridNode = function(element)
1614 {
1615     this._summaryBarElement = element;
1616     WebInspector.DataGridNode.call(this, {summaryRow: true});
1617 }
1618
1619 WebInspector.NetworkTotalGridNode.prototype = {
1620     createCells: function()
1621     {
1622         var td = document.createElement("td");
1623         td.setAttribute("colspan", 7);
1624         td.className = "network-summary";
1625         td.appendChild(this._summaryBarElement);
1626         this._element.appendChild(td);
1627     }
1628 }
1629
1630 WebInspector.NetworkTotalGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;