OSDN Git Service

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