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