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