UNPKG

node-inspector-sans-ws

Version:
1,278 lines (1,086 loc) 93.1 kB
/* * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> * Copyright (C) 2011 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ importScript("RequestView.js"); importScript("NetworkItemView.js"); importScript("RequestCookiesView.js"); importScript("RequestHeadersView.js"); importScript("RequestHTMLView.js"); importScript("RequestJSONView.js"); importScript("RequestPreviewView.js"); importScript("RequestResponseView.js"); importScript("RequestTimingView.js"); importScript("ResourceWebSocketFrameView.js"); /** * @constructor * @extends {WebInspector.View} * @param {WebInspector.Setting} coulmnsVisibilitySetting */ WebInspector.NetworkLogView = function(coulmnsVisibilitySetting) { WebInspector.View.call(this); this.registerRequiredCSS("networkLogView.css"); this._coulmnsVisibilitySetting = coulmnsVisibilitySetting; this._allowRequestSelection = false; this._requests = []; this._requestsById = {}; this._requestsByURL = {}; this._staleRequests = {}; this._requestGridNodes = {}; this._lastRequestGridNodeId = 0; this._mainRequestLoadTime = -1; this._mainRequestDOMContentLoadedTime = -1; this._typeFilterElements = {}; this._typeFilter = WebInspector.NetworkLogView._trivialTypeFilter; this._matchedRequests = []; this._highlightedSubstringChanges = []; this._filteredOutRequests = new Map(); this._matchedRequestsMap = {}; this._currentMatchedRequestIndex = -1; this._createStatusbarButtons(); this._createStatusBarItems(); this._linkifier = new WebInspector.Linkifier(); WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestStarted, this._onRequestStarted, this); WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestUpdated, this._onRequestUpdated, this); WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestUpdated, this); WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this); WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.Load, this._loadEventFired, this); WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, this._domContentLoadedEventFired, this); this._initializeView(); WebInspector.networkLog.requests.forEach(this._appendRequest.bind(this)); } WebInspector.NetworkLogView.HTTPSchemas = {"http": true, "https": true, "ws": true, "wss": true}; WebInspector.NetworkLogView._responseHeaderColumns = ["Cache-Control", "Connection", "Content-Encoding", "Content-Length", "ETag", "Keep-Alive", "Last-Modified", "Server", "Vary"]; WebInspector.NetworkLogView._defaultColumnsVisibility = { method: true, status: true, scheme: false, domain: false, type: true, initiator: true, cookies: false, setCookies: false, size: true, time: true, "Cache-Control": false, "Connection": false, "Content-Encoding": false, "Content-Length": false, "ETag": false, "Keep-Alive": false, "Last-Modified": false, "Server": false, "Vary": false }; WebInspector.NetworkLogView._defaultRefreshDelay = 500; WebInspector.NetworkLogView.ALL_TYPES = "all"; WebInspector.NetworkLogView.prototype = { _initializeView: function() { this.element.id = "network-container"; this._createSortingFunctions(); this._createTable(); this._createTimelineGrid(); this._createSummaryBar(); if (!this.useLargeRows) this._setLargerRequests(this.useLargeRows); this._allowPopover = true; this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), this._onHidePopover.bind(this)); // Enable faster hint. this._popoverHelper.setTimeout(100); this.calculator = new WebInspector.NetworkTransferTimeCalculator(); this._toggleTypeFilter(WebInspector.NetworkLogView.ALL_TYPES, false); this.switchToDetailedView(); }, get statusBarItems() { return [this._largerRequestsButton.element, this._preserveLogToggle.element, this._clearButton.element, this._filterBarElement, this._progressBarContainer]; }, get useLargeRows() { return WebInspector.settings.resourcesLargeRows.get(); }, set allowPopover(flag) { this._allowPopover = flag; }, elementsToRestoreScrollPositionsFor: function() { if (!this._dataGrid) // Not initialized yet. return []; return [this._dataGrid.scrollContainer]; }, onResize: function() { this._updateOffscreenRows(); }, _createTimelineGrid: function() { this._timelineGrid = new WebInspector.TimelineGrid(); this._timelineGrid.element.addStyleClass("network-timeline-grid"); this._dataGrid.element.appendChild(this._timelineGrid.element); }, _createTable: function() { var columns = []; columns.push({ id: "name", titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path")), title: WebInspector.UIString("Name"), sortable: true, weight: 20, disclosure: true }); columns.push({ id: "method", title: WebInspector.UIString("Method"), sortable: true, weight: 6 }); columns.push({ id: "status", titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text")), title: WebInspector.UIString("Status"), sortable: true, weight: 6 }); columns.push({ id: "scheme", title: WebInspector.UIString("Scheme"), sortable: true, weight: 6 }); columns.push({ id: "domain", title: WebInspector.UIString("Domain"), sortable: true, weight: 6 }); columns.push({ id: "type", title: WebInspector.UIString("Type"), sortable: true, weight: 6 }); columns.push({ id: "initiator", title: WebInspector.UIString("Initiator"), sortable: true, weight: 10 }); columns.push({ id: "cookies", title: WebInspector.UIString("Cookies"), sortable: true, weight: 6, align: WebInspector.DataGrid.Align.Right }); columns.push({ id: "setCookies", title: WebInspector.UIString("Set-Cookies"), sortable: true, weight: 6, align: WebInspector.DataGrid.Align.Right }); columns.push({ id: "size", titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Content")), title: WebInspector.UIString("Size"), sortable: true, weight: 6, align: WebInspector.DataGrid.Align.Right }); columns.push({ id: "time", titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Latency")), title: WebInspector.UIString("Time"), sortable: true, weight: 6, align: WebInspector.DataGrid.Align.Right }); var responseHeaderColumns = WebInspector.NetworkLogView._responseHeaderColumns; for (var i = 0; i < responseHeaderColumns.length; ++i) { var headerName = responseHeaderColumns[i]; var descriptor = { id: headerName, title: WebInspector.UIString(headerName), weight: 6 } if (headerName === "Content-Length") descriptor.align = WebInspector.DataGrid.Align.Right; columns.push(descriptor); } columns.push({ id: "timeline", titleDOMFragment: document.createDocumentFragment(), title: WebInspector.UIString("Timeline"), sortable: false, weight: 40, sort: WebInspector.DataGrid.Order.Ascending }); this._dataGrid = new WebInspector.DataGrid(columns); this._dataGrid.setName("networkLog"); this._dataGrid.resizeMethod = WebInspector.DataGrid.ResizeMethod.Last; this._dataGrid.element.addStyleClass("network-log-grid"); this._dataGrid.element.addEventListener("contextmenu", this._contextMenu.bind(this), true); this._dataGrid.show(this.element); // Event listeners need to be added _after_ we attach to the document, so that owner document is properly update. this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortItems, this); this._dataGrid.addEventListener(WebInspector.DataGrid.Events.ColumnsResized, this._updateDividersIfNeeded, this); this._dataGrid.scrollContainer.addEventListener("scroll", this._updateOffscreenRows.bind(this)); this._patchTimelineHeader(); }, _makeHeaderFragment: function(title, subtitle) { var fragment = document.createDocumentFragment(); fragment.createTextChild(title); var subtitleDiv = fragment.createChild("div", "network-header-subtitle"); subtitleDiv.createTextChild(subtitle); return fragment; }, _patchTimelineHeader: function() { var timelineSorting = document.createElement("select"); var option = document.createElement("option"); option.value = "startTime"; option.label = WebInspector.UIString("Timeline"); timelineSorting.appendChild(option); option = document.createElement("option"); option.value = "startTime"; option.label = WebInspector.UIString("Start Time"); timelineSorting.appendChild(option); option = document.createElement("option"); option.value = "responseTime"; option.label = WebInspector.UIString("Response Time"); timelineSorting.appendChild(option); option = document.createElement("option"); option.value = "endTime"; option.label = WebInspector.UIString("End Time"); timelineSorting.appendChild(option); option = document.createElement("option"); option.value = "duration"; option.label = WebInspector.UIString("Duration"); timelineSorting.appendChild(option); option = document.createElement("option"); option.value = "latency"; option.label = WebInspector.UIString("Latency"); timelineSorting.appendChild(option); var header = this._dataGrid.headerTableHeader("timeline"); header.replaceChild(timelineSorting, header.firstChild); timelineSorting.addEventListener("click", function(event) { event.consume() }, false); timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false); this._timelineSortSelector = timelineSorting; }, _createSortingFunctions: function() { this._sortingFunctions = {}; this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator; this._sortingFunctions.method = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "method", false); this._sortingFunctions.status = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "statusCode", false); this._sortingFunctions.scheme = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "scheme", false); this._sortingFunctions.domain = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "domain", false); this._sortingFunctions.type = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "mimeType", false); this._sortingFunctions.initiator = WebInspector.NetworkDataGridNode.InitiatorComparator; this._sortingFunctions.cookies = WebInspector.NetworkDataGridNode.RequestCookiesCountComparator; this._sortingFunctions.setCookies = WebInspector.NetworkDataGridNode.ResponseCookiesCountComparator; this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator; this._sortingFunctions.time = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "duration", false); this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "startTime", false); this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "startTime", false); this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "endTime", false); this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "responseReceivedTime", false); this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "duration", true); this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "latency", true); var timeCalculator = new WebInspector.NetworkTransferTimeCalculator(); var durationCalculator = new WebInspector.NetworkTransferDurationCalculator(); this._calculators = {}; this._calculators.timeline = timeCalculator; this._calculators.startTime = timeCalculator; this._calculators.endTime = timeCalculator; this._calculators.responseTime = timeCalculator; this._calculators.duration = durationCalculator; this._calculators.latency = durationCalculator; }, _sortItems: function() { this._removeAllNodeHighlights(); var columnIdentifier = this._dataGrid.sortColumnIdentifier(); if (columnIdentifier === "timeline") { this._sortByTimeline(); return; } var sortingFunction = this._sortingFunctions[columnIdentifier]; if (!sortingFunction) return; this._dataGrid.sortNodes(sortingFunction, !this._dataGrid.isSortOrderAscending()); this._timelineSortSelector.selectedIndex = 0; this._updateOffscreenRows(); this.searchCanceled(); WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { action: WebInspector.UserMetrics.UserActionNames.NetworkSort, column: columnIdentifier, sortOrder: this._dataGrid.sortOrder() }); }, _sortByTimeline: function() { this._removeAllNodeHighlights(); var selectedIndex = this._timelineSortSelector.selectedIndex; if (!selectedIndex) selectedIndex = 1; // Sort by start time by default. var selectedOption = this._timelineSortSelector[selectedIndex]; var value = selectedOption.value; var sortingFunction = this._sortingFunctions[value]; this._dataGrid.sortNodes(sortingFunction); this.calculator = this._calculators[value]; if (this.calculator.startAtZero) this._timelineGrid.hideEventDividers(); else this._timelineGrid.showEventDividers(); this._dataGrid.markColumnAsSortedBy("timeline", WebInspector.DataGrid.Order.Ascending); this._updateOffscreenRows(); }, /** * @param {string} typeName * @param {string} label */ _addTypeFilter: function(typeName, label) { var typeFilterElement = this._filterBarElement.createChild("li", typeName); typeFilterElement.typeName = typeName; typeFilterElement.createTextChild(label); typeFilterElement.addEventListener("click", this._onTypeFilterClicked.bind(this), false); this._typeFilterElements[typeName] = typeFilterElement; }, _createStatusBarItems: function() { var filterBarElement = document.createElement("div"); filterBarElement.className = "scope-bar status-bar-item"; filterBarElement.title = WebInspector.UIString("Use %s Click to select multiple types.", WebInspector.KeyboardShortcut.shortcutToString("", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta)); this._filterBarElement = filterBarElement; this._addTypeFilter(WebInspector.NetworkLogView.ALL_TYPES, WebInspector.UIString("All")); filterBarElement.createChild("div", "scope-bar-divider"); for (var typeId in WebInspector.resourceTypes) { var type = WebInspector.resourceTypes[typeId]; this._addTypeFilter(type.name(), type.categoryTitle()); } this._progressBarContainer = document.createElement("div"); this._progressBarContainer.className = "status-bar-item"; }, _createSummaryBar: function() { var tbody = this._dataGrid.dataTableBody; var tfoot = document.createElement("tfoot"); var tr = tfoot.createChild("tr", "revealed network-summary-bar"); var td = tr.createChild("td"); td.setAttribute("colspan", 7); tbody.parentNode.insertBefore(tfoot, tbody); this._summaryBarElement = td; }, _updateSummaryBar: function() { var requestsNumber = this._requests.length; if (!requestsNumber) { if (this._summaryBarElement._isDisplayingWarning) return; this._summaryBarElement._isDisplayingWarning = true; this._summaryBarElement.removeChildren(); this._summaryBarElement.createChild("div", "warning-icon-small"); this._summaryBarElement.appendChild(document.createTextNode( WebInspector.UIString("No requests captured. Reload the page to see detailed information on the network activity."))); return; } delete this._summaryBarElement._isDisplayingWarning; var transferSize = 0; var selectedRequestsNumber = 0; var selectedTransferSize = 0; var baseTime = -1; var maxTime = -1; for (var i = 0; i < this._requests.length; ++i) { var request = this._requests[i]; var requestTransferSize = request.transferSize; transferSize += requestTransferSize; if (!this._filteredOutRequests.get(request)) { selectedRequestsNumber++; selectedTransferSize += requestTransferSize; } if (request.url === WebInspector.inspectedPageURL) baseTime = request.startTime; if (request.endTime > maxTime) maxTime = request.endTime; } var text = ""; if (selectedRequestsNumber !== requestsNumber) { text += String.sprintf(WebInspector.UIString("%d / %d requests"), selectedRequestsNumber, requestsNumber); text += " \u2758 " + String.sprintf(WebInspector.UIString("%s / %s transferred"), Number.bytesToString(selectedTransferSize), Number.bytesToString(transferSize)); } else { text += String.sprintf(WebInspector.UIString("%d requests"), requestsNumber); text += " \u2758 " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize)); } if (baseTime !== -1 && this._mainRequestLoadTime !== -1 && this._mainRequestDOMContentLoadedTime !== -1 && this._mainRequestDOMContentLoadedTime > baseTime) { text += " \u2758 " + String.sprintf(WebInspector.UIString("%s (load: %s, DOMContentLoaded: %s)"), Number.secondsToString(maxTime - baseTime), Number.secondsToString(this._mainRequestLoadTime - baseTime), Number.secondsToString(this._mainRequestDOMContentLoadedTime - baseTime)); } this._summaryBarElement.textContent = text; }, /** * @param {!Event} e */ _onTypeFilterClicked: function(e) { var toggle; if (WebInspector.isMac()) toggle = e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey; else toggle = e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey; this._toggleTypeFilter(e.target.typeName, toggle); this._removeAllNodeHighlights(); this.searchCanceled(); this._filterRequests(); }, /** * @param {string} typeName * @param {boolean} allowMultiSelect */ _toggleTypeFilter: function(typeName, allowMultiSelect) { if (allowMultiSelect && typeName !== WebInspector.NetworkLogView.ALL_TYPES) this._typeFilterElements[WebInspector.NetworkLogView.ALL_TYPES].removeStyleClass("selected"); else { for (var key in this._typeFilterElements) this._typeFilterElements[key].removeStyleClass("selected"); } var filterElement = this._typeFilterElements[typeName]; filterElement.enableStyleClass("selected", !filterElement.hasStyleClass("selected")); var allowedTypes = {}; for (var key in this._typeFilterElements) { if (this._typeFilterElements[key].hasStyleClass("selected")) allowedTypes[key] = true; } if (typeName === WebInspector.NetworkLogView.ALL_TYPES) this._typeFilter = WebInspector.NetworkLogView._trivialTypeFilter; else this._typeFilter = WebInspector.NetworkLogView._typeFilter.bind(null, allowedTypes); }, _scheduleRefresh: function() { if (this._needsRefresh) return; this._needsRefresh = true; if (this.isShowing() && !this._refreshTimeout) this._refreshTimeout = setTimeout(this.refresh.bind(this), WebInspector.NetworkLogView._defaultRefreshDelay); }, _updateDividersIfNeeded: function() { if (!this._dataGrid) return; var timelineColumn = this._dataGrid.columns.timeline; for (var i = 0; i < this._dataGrid.resizers.length; ++i) { if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnIndex) { // Position timline grid location. this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left; } } var proceed = true; if (!this.isShowing()) { this._scheduleRefresh(); proceed = false; } else { this.calculator.setDisplayWindow(this._timelineGrid.dividersElement.clientWidth); proceed = this._timelineGrid.updateDividers(this.calculator); } if (!proceed) return; if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) { // If our current sorting method starts at zero, that means it shows all // requests starting at the same point, and so onLoad event and DOMContent // event lines really wouldn't make much sense here, so don't render them. // Additionally, if the calculator doesn't have the computePercentageFromEventTime // function defined, we are probably sorting by size, and event times aren't relevant // in this case. return; } this._timelineGrid.removeEventDividers(); if (this._mainRequestLoadTime !== -1) { var percent = this.calculator.computePercentageFromEventTime(this._mainRequestLoadTime); var loadDivider = document.createElement("div"); loadDivider.className = "network-event-divider network-red-divider"; var loadDividerPadding = document.createElement("div"); loadDividerPadding.className = "network-event-divider-padding"; loadDividerPadding.title = WebInspector.UIString("Load event fired"); loadDividerPadding.appendChild(loadDivider); loadDividerPadding.style.left = percent + "%"; this._timelineGrid.addEventDivider(loadDividerPadding); } if (this._mainRequestDOMContentLoadedTime !== -1) { var percent = this.calculator.computePercentageFromEventTime(this._mainRequestDOMContentLoadedTime); var domContentLoadedDivider = document.createElement("div"); domContentLoadedDivider.className = "network-event-divider network-blue-divider"; var domContentLoadedDividerPadding = document.createElement("div"); domContentLoadedDividerPadding.className = "network-event-divider-padding"; domContentLoadedDividerPadding.title = WebInspector.UIString("DOMContentLoaded event fired"); domContentLoadedDividerPadding.appendChild(domContentLoadedDivider); domContentLoadedDividerPadding.style.left = percent + "%"; this._timelineGrid.addEventDivider(domContentLoadedDividerPadding); } }, _refreshIfNeeded: function() { if (this._needsRefresh) this.refresh(); }, _invalidateAllItems: function() { for (var i = 0; i < this._requests.length; ++i) { var request = this._requests[i]; this._staleRequests[request.requestId] = request; } }, get calculator() { return this._calculator; }, set calculator(x) { if (!x || this._calculator === x) return; this._calculator = x; this._calculator.reset(); this._invalidateAllItems(); this.refresh(); }, _requestGridNode: function(request) { return this._requestGridNodes[request.__gridNodeId]; }, _createRequestGridNode: function(request) { var node = new WebInspector.NetworkDataGridNode(this, request); request.__gridNodeId = this._lastRequestGridNodeId++; this._requestGridNodes[request.__gridNodeId] = node; return node; }, _createStatusbarButtons: function() { this._preserveLogToggle = new WebInspector.StatusBarButton(WebInspector.UIString("Preserve Log upon Navigation"), "record-profile-status-bar-item"); this._preserveLogToggle.addEventListener("click", this._onPreserveLogClicked, this); this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item"); this._clearButton.addEventListener("click", this._reset, this); this._largerRequestsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item"); this._largerRequestsButton.toggled = WebInspector.settings.resourcesLargeRows.get(); this._largerRequestsButton.addEventListener("click", this._toggleLargerRequests, this); }, _loadEventFired: function(event) { this._mainRequestLoadTime = event.data || -1; // Schedule refresh to update boundaries and draw the new line. this._scheduleRefresh(); }, _domContentLoadedEventFired: function(event) { this._mainRequestDOMContentLoadedTime = event.data || -1; // Schedule refresh to update boundaries and draw the new line. this._scheduleRefresh(); }, wasShown: function() { this._refreshIfNeeded(); }, willHide: function() { this._popoverHelper.hidePopover(); }, refresh: function() { this._needsRefresh = false; if (this._refreshTimeout) { clearTimeout(this._refreshTimeout); delete this._refreshTimeout; } this._removeAllNodeHighlights(); var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow(); var boundariesChanged = false; if (this.calculator.updateBoundariesForEventTime) { boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainRequestLoadTime) || boundariesChanged; boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime) || boundariesChanged; } for (var requestId in this._staleRequests) { var request = this._staleRequests[requestId]; var node = this._requestGridNode(request); if (!node) { // Create the timeline tree element and graph. node = this._createRequestGridNode(request); this._dataGrid.rootNode().appendChild(node); } node.refreshRequest(); this._applyFilter(node); if (this.calculator.updateBoundaries(request)) boundariesChanged = true; if (!node.isFilteredOut()) this._updateHighlightIfMatched(request); } if (boundariesChanged) { // The boundaries changed, so all item graphs are stale. this._invalidateAllItems(); } for (var requestId in this._staleRequests) this._requestGridNode(this._staleRequests[requestId]).refreshGraph(this.calculator); this._staleRequests = {}; this._sortItems(); this._updateSummaryBar(); this._dataGrid.updateWidths(); // FIXME: evaluate performance impact of moving this before a call to sortItems() if (wasScrolledToLastRow) this._dataGrid.scrollToLastRow(); }, _onPreserveLogClicked: function(e) { this._preserveLogToggle.toggled = !this._preserveLogToggle.toggled; }, _reset: function() { this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ViewCleared); this._clearSearchMatchedList(); if (this._popoverHelper) this._popoverHelper.hidePopover(); if (this._calculator) this._calculator.reset(); this._requests = []; this._requestsById = {}; this._requestsByURL = {}; this._staleRequests = {}; this._requestGridNodes = {}; if (this._dataGrid) { this._dataGrid.rootNode().removeChildren(); this._updateDividersIfNeeded(); this._updateSummaryBar(); } this._mainRequestLoadTime = -1; this._mainRequestDOMContentLoadedTime = -1; }, get requests() { return this._requests; }, requestById: function(id) { return this._requestsById[id]; }, _onRequestStarted: function(event) { this._appendRequest(event.data); }, _appendRequest: function(request) { this._requests.push(request); // In case of redirect request id is reassigned to a redirected // request and we need to update _requestsById and search results. if (this._requestsById[request.requestId]) { var oldRequest = request.redirects[request.redirects.length - 1]; this._requestsById[oldRequest.requestId] = oldRequest; this._updateSearchMatchedListAfterRequestIdChanged(request.requestId, oldRequest.requestId); } this._requestsById[request.requestId] = request; this._requestsByURL[request.url] = request; // Pull all the redirects of the main request upon commit load. if (request.redirects) { for (var i = 0; i < request.redirects.length; ++i) this._refreshRequest(request.redirects[i]); } this._refreshRequest(request); }, /** * @param {WebInspector.Event} event */ _onRequestUpdated: function(event) { var request = /** @type {WebInspector.NetworkRequest} */ (event.data); this._refreshRequest(request); }, /** * @param {WebInspector.NetworkRequest} request */ _refreshRequest: function(request) { this._staleRequests[request.requestId] = request; this._scheduleRefresh(); }, clear: function() { if (this._preserveLogToggle.toggled) return; this._reset(); }, _mainFrameNavigated: function(event) { if (this._preserveLogToggle.toggled) return; var frame = /** @type {WebInspector.ResourceTreeFrame} */ (event.data); var loaderId = frame.loaderId; // Preserve provisional load requests. var requestsToPreserve = []; for (var i = 0; i < this._requests.length; ++i) { var request = this._requests[i]; if (request.loaderId === loaderId) requestsToPreserve.push(request); } this._reset(); // Restore preserved items. for (var i = 0; i < requestsToPreserve.length; ++i) this._appendRequest(requestsToPreserve[i]); }, switchToDetailedView: function() { if (!this._dataGrid) return; if (this._dataGrid.selectedNode) this._dataGrid.selectedNode.selected = false; this.element.removeStyleClass("brief-mode"); this._detailedMode = true; this._updateColumns(); }, switchToBriefView: function() { this.element.addStyleClass("brief-mode"); this._removeAllNodeHighlights(); this._detailedMode = false; this._updateColumns(); this._popoverHelper.hidePopover(); }, _toggleLargerRequests: function() { WebInspector.settings.resourcesLargeRows.set(!WebInspector.settings.resourcesLargeRows.get()); this._setLargerRequests(WebInspector.settings.resourcesLargeRows.get()); }, _setLargerRequests: function(enabled) { this._largerRequestsButton.toggled = enabled; if (!enabled) { this._largerRequestsButton.title = WebInspector.UIString("Use large resource rows."); this._dataGrid.element.addStyleClass("small"); this._timelineGrid.element.addStyleClass("small"); } else { this._largerRequestsButton.title = WebInspector.UIString("Use small resource rows."); this._dataGrid.element.removeStyleClass("small"); this._timelineGrid.element.removeStyleClass("small"); } this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, { largeRows: enabled }); this._updateOffscreenRows(); }, _getPopoverAnchor: function(element) { if (!this._allowPopover) return; var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label"); if (anchor && anchor.parentElement.request && anchor.parentElement.request.timing) return anchor; anchor = element.enclosingNodeOrSelfWithClass("network-script-initiated"); if (anchor && anchor.request && anchor.request.initiator) return anchor; return null; }, /** * @param {Element} anchor * @param {WebInspector.Popover} popover */ _showPopover: function(anchor, popover) { var content; if (anchor.hasStyleClass("network-script-initiated")) content = this._generateScriptInitiatedPopoverContent(anchor.request); else content = WebInspector.RequestTimingView.createTimingTable(anchor.parentElement.request); popover.show(content, anchor); }, _onHidePopover: function() { this._linkifier.reset(); }, /** * @param {!WebInspector.NetworkRequest} request * @return {!Element} */ _generateScriptInitiatedPopoverContent: function(request) { var stackTrace = request.initiator.stackTrace; var framesTable = document.createElement("table"); for (var i = 0; i < stackTrace.length; ++i) { var stackFrame = stackTrace[i]; var row = document.createElement("tr"); row.createChild("td").textContent = stackFrame.functionName ? stackFrame.functionName : WebInspector.UIString("(anonymous function)"); row.createChild("td").textContent = " @ "; row.createChild("td").appendChild(this._linkifier.linkifyLocation(stackFrame.url, stackFrame.lineNumber - 1, stackFrame.columnNumber - 1)); framesTable.appendChild(row); } return framesTable; }, _updateColumns: function() { var columnsVisibility = this._coulmnsVisibilitySetting.get(); var detailedMode = !!this._detailedMode; for (var columnIdentifier in columnsVisibility) { var visible = detailedMode && columnsVisibility[columnIdentifier]; this._dataGrid.setColumnVisible(columnIdentifier, visible); } this._dataGrid.setColumnVisible("timeline", detailedMode); this._dataGrid.applyColumnWeights(); }, /** * @param {string} columnIdentifier */ _toggleColumnVisibility: function(columnIdentifier) { var columnsVisibility = this._coulmnsVisibilitySetting.get(); columnsVisibility[columnIdentifier] = !columnsVisibility[columnIdentifier]; this._coulmnsVisibilitySetting.set(columnsVisibility); this._updateColumns(); }, /** * @return {!Array.<string>} */ _getConfigurableColumnIDs: function() { if (this._configurableColumnIDs) return this._configurableColumnIDs; var columns = this._dataGrid.columns; function compare(id1, id2) { return columns[id1].title.compareTo(columns[id2].title); } var columnIDs = Object.keys(this._coulmnsVisibilitySetting.get()); this._configurableColumnIDs = columnIDs.sort(compare); return this._configurableColumnIDs; }, _contextMenu: function(event) { var contextMenu = new WebInspector.ContextMenu(event); if (this._detailedMode && event.target.isSelfOrDescendant(this._dataGrid.headerTableBody)) { var columnsVisibility = this._coulmnsVisibilitySetting.get(); var columnIDs = this._getConfigurableColumnIDs(); for (var i = 0; i < columnIDs.length; ++i) { var columnIdentifier = columnIDs[i]; var column = this._dataGrid.columns[columnIdentifier]; contextMenu.appendCheckboxItem(column.title, this._toggleColumnVisibility.bind(this, columnIdentifier), !!columnsVisibility[columnIdentifier]); } contextMenu.show(); return; } var gridNode = this._dataGrid.dataGridNodeFromNode(event.target); var request = gridNode && gridNode._request; if (request) { contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, request.url, false)); contextMenu.appendSeparator(); contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), this._copyLocation.bind(this, request)); if (request.requestHeadersText) contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy request headers" : "Copy Request Headers"), this._copyRequestHeaders.bind(this, request)); if (request.responseHeadersText) contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy response headers" : "Copy Response Headers"), this._copyResponseHeaders.bind(this, request)); contextMenu.appendItem(WebInspector.UIString("Copy as cURL"), this._copyCurlCommand.bind(this, request)); } contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy all as HAR" : "Copy All as HAR"), this._copyAll.bind(this)); contextMenu.appendSeparator(); contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as HAR with content" : "Save as HAR with Content"), this._exportAll.bind(this)); contextMenu.appendSeparator(); contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cache" : "Clear Browser Cache"), this._clearBrowserCache.bind(this)); contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cookies" : "Clear Browser Cookies"), this._clearBrowserCookies.bind(this)); if (request && request.type === WebInspector.resourceTypes.XHR) { contextMenu.appendSeparator(); contextMenu.appendItem(WebInspector.UIString("Replay XHR"), this._replayXHR.bind(this, request.requestId)); contextMenu.appendSeparator(); } contextMenu.show(); }, _replayXHR: function(requestId) { NetworkAgent.replayXHR(requestId); }, _copyAll: function() { var harArchive = { log: (new WebInspector.HARLog(this._requests.filter(WebInspector.NetworkLogView.HTTPRequestsFilter))).build() }; InspectorFrontendHost.copyText(JSON.stringify(harArchive, null, 2)); }, _copyLocation: function(request) { InspectorFrontendHost.copyText(request.url); }, _copyRequestHeaders: function(request) { InspectorFrontendHost.copyText(request.requestHeadersText); }, _copyResponseHeaders: function(request) { InspectorFrontendHost.copyText(request.responseHeadersText); }, /** * @param {WebInspector.NetworkRequest} request */ _copyCurlCommand: function(request) { InspectorFrontendHost.copyText(this._generateCurlCommand(request)); }, _exportAll: function() { var filename = WebInspector.inspectedPageDomain + ".har"; var stream = new WebInspector.FileOutputStream(); stream.open(filename, openCallback.bind(this)); function openCallback() { var progressIndicator = new WebInspector.ProgressIndicator(); this._progressBarContainer.appendChild(progressIndicator.element); var harWriter = new WebInspector.HARWriter(); harWriter.write(stream, this._requests.filter(WebInspector.NetworkLogView.HTTPRequestsFilter), progressIndicator); } }, _clearBrowserCache: function() { if (confirm(WebInspector.UIString("Are you sure you want to clear browser cache?"))) NetworkAgent.clearBrowserCache(); }, _clearBrowserCookies: function() { if (confirm(WebInspector.UIString("Are you sure you want to clear browser cookies?"))) NetworkAgent.clearBrowserCookies(); }, _updateOffscreenRows: function() { var dataTableBody = this._dataGrid.dataTableBody; var rows = dataTableBody.children; var recordsCount = rows.length; if (recordsCount < 2) return; // Filler row only. var visibleTop = this._dataGrid.scrollContainer.scrollTop; var visibleBottom = visibleTop + this._dataGrid.scrollContainer.offsetHeight; var rowHeight = 0; // Filler is at recordsCount - 1. var unfilteredRowIndex = 0; for (var i = 0; i < recordsCount - 1; ++i) { var row = rows[i]; var dataGridNode = this._dataGrid.dataGridNodeFromNode(row); if (dataGridNode.isFilteredOut()) { row.removeStyleClass("offscreen"); continue; } if (!rowHeight) rowHeight = row.offsetHeight; var rowIsVisible = unfilteredRowIndex * rowHeight < visibleBottom && (unfilteredRowIndex + 1) * rowHeight > visibleTop; if (rowIsVisible !== row.rowIsVisible) { row.enableStyleClass("offscreen", !rowIsVisible); row.rowIsVisible = rowIsVisible; } unfilteredRowIndex++; } }, _matchRequest: function(request) { if (!this._searchRegExp) return -1; if (!request.name().match(this._searchRegExp) && !request.path().match(this._searchRegExp)) return -1; if (request.requestId in this._matchedRequestsMap) return this._matchedRequestsMap[request.requestId]; var matchedRequestIndex = this._matchedRequests.length; this._matchedRequestsMap[request.requestId] = matchedRequestIndex; this._matchedRequests.push(request.requestId); return matchedRequestIndex; }, _clearSearchMatchedList: function() { delete this._searchRegExp; this._matchedRequests = []; this._matchedRequestsMap = {}; this._removeAllHighlights(); }, _updateSearchMatchedListAfterRequestIdChanged: function(oldRequestId, newRequestId) { var requestIndex = this._matchedRequestsMap[oldRequestId]; if (requestIndex) { delete this._matchedRequestsMap[oldRequestId]; this._matchedRequestsMap[newRequestId] = requestIndex; this._matchedRequests[requestIndex] = newRequestId; } }, _updateHighlightIfMatched: function(request) { var matchedRequestIndex = this._matchRequest(request); if (matchedRequestIndex === -1) return; this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedRequests.length); if (this._currentMatchedRequestIndex !== -1 && this._currentMatchedRequestIndex !== matchedRequestIndex) return; this._highlightNthMatchedRequestForSearch(matchedRequestIndex, false); }, _removeAllHighlights: function() { this._removeAllNodeHighlights(); for (var i = 0; i < this._highlightedSubstringChanges.length; ++i) WebInspector.revertDomChanges(this._highlightedSubstringChanges[i]); this._highlightedSubstringChanges = []; }, /** * @param {WebInspector.NetworkRequest} request * @param {boolean} reveal * @param {RegExp=} regExp */ _highlightMatchedRequest: function(request, reveal, regExp) { var node = this._requestGridNode(request); if (!node) return; var nameMatched = request.name().match(regExp); var pathMatched = request.path().match(regExp); if (!nameMatched && pathMatched && !this._largerRequestsButton.toggled) this._toggleLargerRequests(); var highlightedSubstringChanges = node._highlightMatchedSubstring(regExp); this._highlightedSubstringChanges.push(highlightedSubstringChanges); if (reveal) { node.reveal(); this._highlightNode(node); } }, /** * @param {number} matchedRequestIndex * @param {boolean} reveal */ _highlightNthMatchedRequestForSearch: function(matchedRequestIndex, reveal) { var request = this.requestById(this._matchedRequests[matchedRequestIndex]); if (!request) return; this._removeAllHighlights(); this._highlightMatchedRequest(request, reveal, this._searchRegExp); var node = this._requestGridNode(request); if (node) this._currentMatchedRequestIndex = matchedRequestIndex; this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._currentMatchedRequestIndex); }, /** * @param {string} query * @param {boolean} shouldJump */ performSearch: function(query, shouldJump) { var newMatchedRequestIndex = 0; var currentMatchedRequestId; if (this._currentMatchedRequestIndex !== -1) currentMatchedRequestId = this._matchedRequests[this._currentMatchedRequestIndex]; this._clearSearchMatchedList(); this._searchRegExp = createPlainTextSearchRegex(query, "i"); var childNodes = this._dataGrid.dataTableBody.childNodes; var requestNodes = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); // drop the filler row. for (var i = 0; i < requestNodes.length; ++i) { var dataGridNode = this._dataGrid.dataGridNodeFromNode(requestNodes[i]); if (dataGridNode.isFilteredOut()) contin