| /* |
| * 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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. |
| */ |
| |
| /** |
| * @implements {SDK.SDKModelObserver<!SDK.NetworkManager>} |
| */ |
| export default class NetworkLog extends Common.Object { |
| constructor() { |
| super(); |
| /** @type {!Array<!SDK.NetworkRequest>} */ |
| this._requests = []; |
| /** @type {!Set<!SDK.NetworkRequest>} */ |
| this._requestsSet = new Set(); |
| /** @type {!Map<!SDK.NetworkManager, !PageLoad>} */ |
| this._pageLoadForManager = new Map(); |
| this._isRecording = true; |
| SDK.targetManager.observeModels(SDK.NetworkManager, this); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.NetworkManager} networkManager |
| */ |
| modelAdded(networkManager) { |
| const eventListeners = []; |
| eventListeners.push( |
| networkManager.addEventListener(SDK.NetworkManager.Events.RequestStarted, this._onRequestStarted, this)); |
| eventListeners.push( |
| networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, this._onRequestUpdated, this)); |
| eventListeners.push( |
| networkManager.addEventListener(SDK.NetworkManager.Events.RequestRedirected, this._onRequestRedirect, this)); |
| eventListeners.push( |
| networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, this._onRequestUpdated, this)); |
| eventListeners.push(networkManager.addEventListener( |
| SDK.NetworkManager.Events.MessageGenerated, this._networkMessageGenerated.bind(this, networkManager))); |
| |
| const resourceTreeModel = networkManager.target().model(SDK.ResourceTreeModel); |
| if (resourceTreeModel) { |
| eventListeners.push( |
| resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.WillReloadPage, this._willReloadPage, this)); |
| eventListeners.push(resourceTreeModel.addEventListener( |
| SDK.ResourceTreeModel.Events.MainFrameNavigated, this._onMainFrameNavigated, this)); |
| eventListeners.push(resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, this._onLoad, this)); |
| eventListeners.push(resourceTreeModel.addEventListener( |
| SDK.ResourceTreeModel.Events.DOMContentLoaded, this._onDOMContentLoaded.bind(this, resourceTreeModel))); |
| } |
| |
| networkManager[_events] = eventListeners; |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.NetworkManager} networkManager |
| */ |
| modelRemoved(networkManager) { |
| this._removeNetworkManagerListeners(networkManager); |
| } |
| |
| /** |
| * @param {!SDK.NetworkManager} networkManager |
| */ |
| _removeNetworkManagerListeners(networkManager) { |
| Common.EventTarget.removeEventListeners(networkManager[_events]); |
| } |
| |
| /** |
| * @param {boolean} enabled |
| */ |
| setIsRecording(enabled) { |
| if (this._isRecording === enabled) { |
| return; |
| } |
| this._isRecording = enabled; |
| if (enabled) { |
| SDK.targetManager.observeModels(SDK.NetworkManager, this); |
| } else { |
| SDK.targetManager.unobserveModels(SDK.NetworkManager, this); |
| SDK.targetManager.models(SDK.NetworkManager).forEach(this._removeNetworkManagerListeners.bind(this)); |
| } |
| } |
| |
| /** |
| * @param {string} url |
| * @return {?SDK.NetworkRequest} |
| */ |
| requestForURL(url) { |
| return this._requests.find(request => request.url() === url) || null; |
| } |
| |
| /** |
| * @return {!Array<!SDK.NetworkRequest>} |
| */ |
| requests() { |
| return this._requests; |
| } |
| |
| /** |
| * @param {!SDK.NetworkManager} networkManager |
| * @param {!Protocol.Network.RequestId} requestId |
| * @return {?SDK.NetworkRequest} |
| */ |
| requestByManagerAndId(networkManager, requestId) { |
| // We itterate backwards because the last item will likely be the one needed for console network request lookups. |
| for (let i = this._requests.length - 1; i >= 0; i--) { |
| const request = this._requests[i]; |
| if (requestId === request.requestId() && networkManager === SDK.NetworkManager.forRequest(request)) { |
| return request; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @param {!SDK.NetworkManager} networkManager |
| * @param {string} url |
| * @return {?SDK.NetworkRequest} |
| */ |
| _requestByManagerAndURL(networkManager, url) { |
| for (const request of this._requests) { |
| if (url === request.url() && networkManager === SDK.NetworkManager.forRequest(request)) { |
| return request; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @param {!SDK.NetworkRequest} request |
| */ |
| _initializeInitiatorSymbolIfNeeded(request) { |
| if (!request[_initiatorDataSymbol]) { |
| /** @type {!{info: ?SDK.NetworkLog._InitiatorInfo, chain: !Set<!SDK.NetworkRequest>, request: (?SDK.NetworkRequest|undefined)}} */ |
| request[_initiatorDataSymbol] = { |
| info: null, |
| chain: null, |
| request: undefined, |
| }; |
| } |
| } |
| |
| /** |
| * @param {!SDK.NetworkRequest} request |
| * @return {!SDK.NetworkLog._InitiatorInfo} |
| */ |
| initiatorInfoForRequest(request) { |
| this._initializeInitiatorSymbolIfNeeded(request); |
| if (request[_initiatorDataSymbol].info) { |
| return request[_initiatorDataSymbol].info; |
| } |
| |
| let type = SDK.NetworkRequest.InitiatorType.Other; |
| let url = ''; |
| let lineNumber = -Infinity; |
| let columnNumber = -Infinity; |
| let scriptId = null; |
| let initiatorStack = null; |
| const initiator = request.initiator(); |
| |
| const redirectSource = request.redirectSource(); |
| if (redirectSource) { |
| type = SDK.NetworkRequest.InitiatorType.Redirect; |
| url = redirectSource.url(); |
| } else if (initiator) { |
| if (initiator.type === Protocol.Network.InitiatorType.Parser) { |
| type = SDK.NetworkRequest.InitiatorType.Parser; |
| url = initiator.url ? initiator.url : url; |
| lineNumber = initiator.lineNumber ? initiator.lineNumber : lineNumber; |
| } else if (initiator.type === Protocol.Network.InitiatorType.Script) { |
| for (let stack = initiator.stack; stack; stack = stack.parent) { |
| const topFrame = stack.callFrames.length ? stack.callFrames[0] : null; |
| if (!topFrame) { |
| continue; |
| } |
| type = SDK.NetworkRequest.InitiatorType.Script; |
| url = topFrame.url || Common.UIString('<anonymous>'); |
| lineNumber = topFrame.lineNumber; |
| columnNumber = topFrame.columnNumber; |
| scriptId = topFrame.scriptId; |
| break; |
| } |
| if (!initiator.stack && initiator.url) { |
| type = SDK.NetworkRequest.InitiatorType.Script; |
| url = initiator.url; |
| lineNumber = initiator.lineNumber || 0; |
| } |
| if (initiator.stack && initiator.stack.callFrames && initiator.stack.callFrames.length) { |
| initiatorStack = initiator.stack || null; |
| } |
| } else if (initiator.type === Protocol.Network.InitiatorType.Preload) { |
| type = SDK.NetworkRequest.InitiatorType.Preload; |
| } else if (initiator.type === Protocol.Network.InitiatorType.SignedExchange) { |
| type = SDK.NetworkRequest.InitiatorType.SignedExchange; |
| url = initiator.url; |
| } |
| } |
| |
| request[_initiatorDataSymbol].info = { |
| type: type, |
| url: url, |
| lineNumber: lineNumber, |
| columnNumber: columnNumber, |
| scriptId: scriptId, |
| stack: initiatorStack |
| }; |
| return request[_initiatorDataSymbol].info; |
| } |
| |
| /** |
| * @param {!SDK.NetworkRequest} request |
| * @return {!SDK.NetworkLog.InitiatorGraph} |
| */ |
| initiatorGraphForRequest(request) { |
| /** @type {!Map<!SDK.NetworkRequest>} */ |
| const initiated = new Map(); |
| const networkManager = SDK.NetworkManager.forRequest(request); |
| for (const otherRequest of this._requests) { |
| const otherRequestManager = SDK.NetworkManager.forRequest(otherRequest); |
| if (networkManager === otherRequestManager && this._initiatorChain(otherRequest).has(request)) { |
| // save parent request of otherRequst in order to build the initiator chain table later |
| initiated.set(otherRequest, this._initiatorRequest(otherRequest)); |
| } |
| } |
| return {initiators: this._initiatorChain(request), initiated: initiated}; |
| } |
| |
| /** |
| * @param {!SDK.NetworkRequest} request |
| * @return {!Set<!SDK.NetworkRequest>} |
| */ |
| _initiatorChain(request) { |
| this._initializeInitiatorSymbolIfNeeded(request); |
| let initiatorChainCache = |
| /** @type {?Set<!SDK.NetworkRequest>} */ (request[_initiatorDataSymbol].chain); |
| if (initiatorChainCache) { |
| return initiatorChainCache; |
| } |
| |
| initiatorChainCache = new Set(); |
| |
| let checkRequest = request; |
| do { |
| this._initializeInitiatorSymbolIfNeeded(checkRequest); |
| if (checkRequest[_initiatorDataSymbol].chain) { |
| initiatorChainCache.addAll(checkRequest[_initiatorDataSymbol].chain); |
| break; |
| } |
| if (initiatorChainCache.has(checkRequest)) { |
| break; |
| } |
| initiatorChainCache.add(checkRequest); |
| checkRequest = this._initiatorRequest(checkRequest); |
| } while (checkRequest); |
| request[_initiatorDataSymbol].chain = initiatorChainCache; |
| return initiatorChainCache; |
| } |
| |
| /** |
| * @param {!SDK.NetworkRequest} request |
| * @return {?SDK.NetworkRequest} |
| */ |
| _initiatorRequest(request) { |
| this._initializeInitiatorSymbolIfNeeded(request); |
| if (request[_initiatorDataSymbol].request !== undefined) { |
| return request[_initiatorDataSymbol].request; |
| } |
| const url = this.initiatorInfoForRequest(request).url; |
| const networkManager = SDK.NetworkManager.forRequest(request); |
| request[_initiatorDataSymbol].request = networkManager ? this._requestByManagerAndURL(networkManager, url) : null; |
| return request[_initiatorDataSymbol].request; |
| } |
| |
| _willReloadPage() { |
| if (!Common.moduleSetting('network_log.preserve-log').get()) { |
| this.reset(); |
| } |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onMainFrameNavigated(event) { |
| const mainFrame = /** @type {!SDK.ResourceTreeFrame} */ (event.data); |
| const manager = mainFrame.resourceTreeModel().target().model(SDK.NetworkManager); |
| if (!manager || mainFrame.resourceTreeModel().target().parentTarget()) { |
| return; |
| } |
| |
| const oldRequests = this._requests; |
| const oldManagerRequests = this._requests.filter(request => SDK.NetworkManager.forRequest(request) === manager); |
| const oldRequestsSet = this._requestsSet; |
| this._requests = []; |
| this._requestsSet = new Set(); |
| this.dispatchEventToListeners(Events.Reset); |
| |
| // Preserve requests from the new session. |
| let currentPageLoad = null; |
| const requestsToAdd = []; |
| for (const request of oldManagerRequests) { |
| if (request.loaderId !== mainFrame.loaderId) { |
| continue; |
| } |
| if (!currentPageLoad) { |
| currentPageLoad = new PageLoad(request); |
| let redirectSource = request.redirectSource(); |
| while (redirectSource) { |
| requestsToAdd.push(redirectSource); |
| redirectSource = redirectSource.redirectSource(); |
| } |
| } |
| requestsToAdd.push(request); |
| } |
| |
| // Preserve service worker requests from the new session. |
| const serviceWorkerRequestsToAdd = []; |
| for (const swRequest of oldRequests) { |
| if (!swRequest.initiatedByServiceWorker()) { |
| continue; |
| } |
| |
| // If there is a matching request that came before this one, keep it. |
| const keepRequest = requestsToAdd.some( |
| request => request.url() === swRequest.url() && request.issueTime() <= swRequest.issueTime()); |
| if (keepRequest) { |
| serviceWorkerRequestsToAdd.push(swRequest); |
| } |
| } |
| requestsToAdd.push(...serviceWorkerRequestsToAdd); |
| |
| for (const request of requestsToAdd) { |
| oldRequestsSet.delete(request); |
| this._requests.push(request); |
| this._requestsSet.add(request); |
| currentPageLoad.bindRequest(request); |
| this.dispatchEventToListeners(Events.RequestAdded, request); |
| } |
| |
| if (Common.moduleSetting('network_log.preserve-log').get()) { |
| for (const request of oldRequestsSet) { |
| this._requests.push(request); |
| this._requestsSet.add(request); |
| this.dispatchEventToListeners(Events.RequestAdded, request); |
| } |
| } |
| |
| if (currentPageLoad) { |
| this._pageLoadForManager.set(manager, currentPageLoad); |
| } |
| } |
| |
| /** |
| * @param {!Array<!SDK.NetworkRequest>} requests |
| */ |
| importRequests(requests) { |
| this.reset(); |
| this._requests = []; |
| this._requestsSet.clear(); |
| for (const request of requests) { |
| this._requests.push(request); |
| this._requestsSet.add(request); |
| this.dispatchEventToListeners(Events.RequestAdded, request); |
| } |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onRequestStarted(event) { |
| const request = /** @type {!SDK.NetworkRequest} */ (event.data); |
| this._requests.push(request); |
| this._requestsSet.add(request); |
| const manager = SDK.NetworkManager.forRequest(request); |
| const pageLoad = manager ? this._pageLoadForManager.get(manager) : null; |
| if (pageLoad) { |
| pageLoad.bindRequest(request); |
| } |
| this.dispatchEventToListeners(Events.RequestAdded, request); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onRequestUpdated(event) { |
| const request = /** @type {!SDK.NetworkRequest} */ (event.data); |
| if (!this._requestsSet.has(request)) { |
| return; |
| } |
| this.dispatchEventToListeners(Events.RequestUpdated, request); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onRequestRedirect(event) { |
| const request = /** @type {!SDK.NetworkRequest} */ (event.data); |
| delete request[_initiatorDataSymbol]; |
| } |
| |
| /** |
| * @param {!SDK.ResourceTreeModel} resourceTreeModel |
| * @param {!Common.Event} event |
| */ |
| _onDOMContentLoaded(resourceTreeModel, event) { |
| const networkManager = resourceTreeModel.target().model(SDK.NetworkManager); |
| const pageLoad = networkManager ? this._pageLoadForManager.get(networkManager) : null; |
| if (pageLoad) { |
| pageLoad.contentLoadTime = /** @type {number} */ (event.data); |
| } |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onLoad(event) { |
| const networkManager = event.data.resourceTreeModel.target().model(SDK.NetworkManager); |
| const pageLoad = networkManager ? this._pageLoadForManager.get(networkManager) : null; |
| if (pageLoad) { |
| pageLoad.loadTime = /** @type {number} */ (event.data.loadTime); |
| } |
| } |
| |
| reset() { |
| this._requests = []; |
| this._requestsSet.clear(); |
| const managers = new Set(SDK.targetManager.models(SDK.NetworkManager)); |
| for (const manager of this._pageLoadForManager.keys()) { |
| if (!managers.has(manager)) { |
| this._pageLoadForManager.delete(manager); |
| } |
| } |
| |
| this.dispatchEventToListeners(Events.Reset); |
| } |
| |
| /** |
| * @param {!SDK.NetworkManager} networkManager |
| * @param {!Common.Event} event |
| */ |
| _networkMessageGenerated(networkManager, event) { |
| const message = /** @type {!SDK.NetworkManager.Message} */ (event.data); |
| const consoleMessage = new SDK.ConsoleMessage( |
| networkManager.target().model(SDK.RuntimeModel), SDK.ConsoleMessage.MessageSource.Network, |
| message.warning ? SDK.ConsoleMessage.MessageLevel.Warning : SDK.ConsoleMessage.MessageLevel.Info, |
| message.message); |
| this.associateConsoleMessageWithRequest(consoleMessage, message.requestId); |
| SDK.consoleModel.addMessage(consoleMessage); |
| } |
| |
| /** |
| * @param {!SDK.ConsoleMessage} consoleMessage |
| * @param {!Protocol.Network.RequestId} requestId |
| */ |
| associateConsoleMessageWithRequest(consoleMessage, requestId) { |
| const target = consoleMessage.target(); |
| const networkManager = target ? target.model(SDK.NetworkManager) : null; |
| if (!networkManager) { |
| return; |
| } |
| const request = this.requestByManagerAndId(networkManager, requestId); |
| if (!request) { |
| return; |
| } |
| consoleMessage[_requestSymbol] = request; |
| const initiator = request.initiator(); |
| if (initiator) { |
| consoleMessage.stackTrace = initiator.stack || undefined; |
| if (initiator.url) { |
| consoleMessage.url = initiator.url; |
| consoleMessage.line = initiator.lineNumber || 0; |
| } |
| } |
| } |
| |
| /** |
| * @param {!SDK.ConsoleMessage} consoleMessage |
| * @return {?SDK.NetworkRequest} |
| */ |
| static requestForConsoleMessage(consoleMessage) { |
| return consoleMessage[_requestSymbol] || null; |
| } |
| } |
| |
| export class PageLoad { |
| /** |
| * @param {!SDK.NetworkRequest} mainRequest |
| */ |
| constructor(mainRequest) { |
| this.id = ++PageLoad._lastIdentifier; |
| this.url = mainRequest.url(); |
| this.startTime = mainRequest.startTime; |
| /** @type {number} */ |
| this.loadTime; |
| /** @type {number} */ |
| this.contentLoadTime; |
| this.mainRequest = mainRequest; |
| |
| this._showDataSaverWarningIfNeeded(); |
| } |
| |
| async _showDataSaverWarningIfNeeded() { |
| const manager = SDK.NetworkManager.forRequest(this.mainRequest); |
| if (!manager) { |
| return; |
| } |
| if (!this.mainRequest.finished) { |
| await this.mainRequest.once(SDK.NetworkRequest.Events.FinishedLoading); |
| } |
| const saveDataHeader = this.mainRequest.requestHeaderValue('Save-Data'); |
| if (!PageLoad._dataSaverMessageWasShown && saveDataHeader && saveDataHeader === 'on') { |
| const message = Common.UIString( |
| 'Consider disabling %s while debugging. For more info see: %s', Common.UIString('Chrome Data Saver'), |
| 'https://support.google.com/chrome/?p=datasaver'); |
| manager.dispatchEventToListeners( |
| SDK.NetworkManager.Events.MessageGenerated, |
| {message: message, requestId: this.mainRequest.requestId(), warning: true}); |
| PageLoad._dataSaverMessageWasShown = true; |
| } |
| } |
| |
| /** |
| * @param {!SDK.NetworkRequest} request |
| * @return {?PageLoad} |
| */ |
| static forRequest(request) { |
| return request[PageLoad._pageLoadForRequestSymbol] || null; |
| } |
| |
| /** |
| * @param {!SDK.NetworkRequest} request |
| */ |
| bindRequest(request) { |
| request[PageLoad._pageLoadForRequestSymbol] = this; |
| } |
| } |
| |
| PageLoad._lastIdentifier = 0; |
| PageLoad._pageLoadForRequestSymbol = Symbol('PageLoadForRequest'); |
| PageLoad._dataSaverMessageWasShown = false; |
| |
| const _requestSymbol = Symbol('_request'); |
| |
| export const Events = { |
| Reset: Symbol('Reset'), |
| RequestAdded: Symbol('RequestAdded'), |
| RequestUpdated: Symbol('RequestUpdated') |
| }; |
| |
| const _initiatorDataSymbol = Symbol('InitiatorData'); |
| const _events = Symbol('SDK.NetworkLog.events'); |
| |
| /* Legacy exported object */ |
| self.SDK = self.SDK || {}; |
| |
| /* Legacy exported object */ |
| SDK = SDK || {}; |
| |
| /** @constructor */ |
| SDK.NetworkLog = NetworkLog; |
| |
| /** @constructor */ |
| SDK.NetworkLog.PageLoad = PageLoad; |
| |
| SDK.NetworkLog.Events = Events; |
| |
| /** @type {!NetworkLog} */ |
| SDK.networkLog = new NetworkLog(); |
| |
| /** @typedef {!{initiators: !Set<!SDK.NetworkRequest>, initiated: !Map<!SDK.NetworkRequest, !SDK.NetworkRequest>}} */ |
| SDK.NetworkLog.InitiatorGraph; |
| |
| /** @typedef {!{type: !SDK.NetworkRequest.InitiatorType, url: string, lineNumber: number, columnNumber: number, scriptId: ?string, stack: ?Protocol.Runtime.StackTrace}} */ |
| SDK.NetworkLog._InitiatorInfo; |