UNPKG

@quick-game/cli

Version:

Command line interface for rapid qg development

530 lines 24.4 kB
/* * 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. */ import * as Common from '../../core/common/common.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as Platform from '../../core/platform/platform.js'; import * as SDK from '../../core/sdk/sdk.js'; const UIStrings = { /** * @description When DevTools doesn't know the URL that initiated a network request, we * show this phrase instead. 'unknown' would also work in this context. */ anonymous: '<anonymous>', }; const str_ = i18n.i18n.registerUIStrings('models/logs/NetworkLog.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); let networkLogInstance; export class NetworkLog extends Common.ObjectWrapper.ObjectWrapper { requestsInternal; sentNetworkRequests; receivedNetworkResponses; requestsSet; requestsMap; pageLoadForManager; isRecording; modelListeners; initiatorData; unresolvedPreflightRequests; constructor() { super(); this.requestsInternal = []; this.sentNetworkRequests = []; this.receivedNetworkResponses = []; this.requestsSet = new Set(); this.requestsMap = new Map(); this.pageLoadForManager = new Map(); this.isRecording = true; this.modelListeners = new WeakMap(); this.initiatorData = new WeakMap(); SDK.TargetManager.TargetManager.instance().observeModels(SDK.NetworkManager.NetworkManager, this); const recordLogSetting = Common.Settings.Settings.instance().moduleSetting('network_log.record-log'); recordLogSetting.addChangeListener(() => { const preserveLogSetting = Common.Settings.Settings.instance().moduleSetting('network_log.preserve-log'); if (!preserveLogSetting.get() && recordLogSetting.get()) { this.reset(true); } this.setIsRecording(recordLogSetting.get()); }, this); this.unresolvedPreflightRequests = new Map(); } static instance() { if (!networkLogInstance) { networkLogInstance = new NetworkLog(); } return networkLogInstance; } static removeInstance() { networkLogInstance = undefined; } 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))); eventListeners.push(networkManager.addEventListener(SDK.NetworkManager.Events.ResponseReceived, this.onResponseReceived, this)); const resourceTreeModel = networkManager.target().model(SDK.ResourceTreeModel.ResourceTreeModel); if (resourceTreeModel) { eventListeners.push(resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.WillReloadPage, this.willReloadPage, this)); eventListeners.push(resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.PrimaryPageChanged, this.onPrimaryPageChanged, 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))); } this.modelListeners.set(networkManager, eventListeners); } modelRemoved(networkManager) { this.removeNetworkManagerListeners(networkManager); } removeNetworkManagerListeners(networkManager) { Common.EventTarget.removeEventListeners(this.modelListeners.get(networkManager) || []); } setIsRecording(enabled) { if (this.isRecording === enabled) { return; } this.isRecording = enabled; if (enabled) { SDK.TargetManager.TargetManager.instance().observeModels(SDK.NetworkManager.NetworkManager, this); } else { SDK.TargetManager.TargetManager.instance().unobserveModels(SDK.NetworkManager.NetworkManager, this); SDK.TargetManager.TargetManager.instance() .models(SDK.NetworkManager.NetworkManager) .forEach(this.removeNetworkManagerListeners.bind(this)); } } requestForURL(url) { return this.requestsInternal.find(request => request.url() === url) || null; } originalRequestForURL(url) { return this.sentNetworkRequests.find(request => request.url === url) || null; } originalResponseForURL(url) { return this.receivedNetworkResponses.find(response => response.url === url) || null; } requests() { return this.requestsInternal; } requestByManagerAndId(networkManager, requestId) { // We iterate backwards because the last item will likely be the one needed for console network request lookups. for (let i = this.requestsInternal.length - 1; i >= 0; i--) { const request = this.requestsInternal[i]; if (requestId === request.requestId() && networkManager === SDK.NetworkManager.NetworkManager.forRequest(request)) { return request; } } return null; } requestByManagerAndURL(networkManager, url) { for (const request of this.requestsInternal) { if (url === request.url() && networkManager === SDK.NetworkManager.NetworkManager.forRequest(request)) { return request; } } return null; } initializeInitiatorSymbolIfNeeded(request) { let initiatorInfo = this.initiatorData.get(request); if (initiatorInfo) { return initiatorInfo; } initiatorInfo = { info: null, chain: null, request: undefined, }; this.initiatorData.set(request, initiatorInfo); return initiatorInfo; } static initiatorInfoForRequest(request, existingInitiatorData) { const initiatorInfo = existingInitiatorData || { info: null, chain: null, request: undefined, }; let type = SDK.NetworkRequest.InitiatorType.Other; let url = Platform.DevToolsPath.EmptyUrlString; let lineNumber = undefined; let columnNumber = undefined; let scriptId = null; let initiatorStack = null; let initiatorRequest = 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 === "parser" /* Protocol.Network.InitiatorType.Parser */) { type = SDK.NetworkRequest.InitiatorType.Parser; url = initiator.url ? initiator.url : url; lineNumber = initiator.lineNumber; columnNumber = initiator.columnNumber; } else if (initiator.type === "script" /* Protocol.Network.InitiatorType.Script */) { for (let stack = initiator.stack; stack;) { const topFrame = stack.callFrames.length ? stack.callFrames[0] : null; if (!topFrame) { stack = stack.parent; continue; } type = SDK.NetworkRequest.InitiatorType.Script; url = (topFrame.url || i18nString(UIStrings.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; } if (initiator.stack?.callFrames?.length) { initiatorStack = initiator.stack; } } else if (initiator.type === "preload" /* Protocol.Network.InitiatorType.Preload */) { type = SDK.NetworkRequest.InitiatorType.Preload; } else if (initiator.type === "preflight" /* Protocol.Network.InitiatorType.Preflight */) { type = SDK.NetworkRequest.InitiatorType.Preflight; initiatorRequest = request.preflightInitiatorRequest(); } else if (initiator.type === "SignedExchange" /* Protocol.Network.InitiatorType.SignedExchange */) { type = SDK.NetworkRequest.InitiatorType.SignedExchange; url = initiator.url || Platform.DevToolsPath.EmptyUrlString; } } initiatorInfo.info = { type, url, lineNumber, columnNumber, scriptId, stack: initiatorStack, initiatorRequest }; return initiatorInfo.info; } initiatorInfoForRequest(request) { const initiatorInfo = this.initializeInitiatorSymbolIfNeeded(request); if (initiatorInfo.info) { return initiatorInfo.info; } return NetworkLog.initiatorInfoForRequest(request, initiatorInfo); } initiatorGraphForRequest(request) { const initiated = new Map(); const networkManager = SDK.NetworkManager.NetworkManager.forRequest(request); for (const otherRequest of this.requestsInternal) { const otherRequestManager = SDK.NetworkManager.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 const initiatorRequest = this.initiatorRequest(otherRequest); if (initiatorRequest) { initiated.set(otherRequest, initiatorRequest); } } } return { initiators: this.initiatorChain(request), initiated: initiated }; } initiatorChain(request) { const initiatorDataForRequest = this.initializeInitiatorSymbolIfNeeded(request); let initiatorChainCache = initiatorDataForRequest.chain; if (initiatorChainCache) { return initiatorChainCache; } initiatorChainCache = new Set(); let checkRequest = request; while (checkRequest) { const initiatorData = this.initializeInitiatorSymbolIfNeeded(checkRequest); if (initiatorData.chain) { Platform.SetUtilities.addAll(initiatorChainCache, initiatorData.chain); break; } if (initiatorChainCache.has(checkRequest)) { break; } initiatorChainCache.add(checkRequest); checkRequest = this.initiatorRequest(checkRequest); } initiatorDataForRequest.chain = initiatorChainCache; return initiatorChainCache; } initiatorRequest(request) { const initiatorData = this.initializeInitiatorSymbolIfNeeded(request); if (initiatorData.request !== undefined) { return initiatorData.request; } const url = this.initiatorInfoForRequest(request).url; const networkManager = SDK.NetworkManager.NetworkManager.forRequest(request); initiatorData.request = networkManager ? this.requestByManagerAndURL(networkManager, url) : null; return initiatorData.request; } willReloadPage() { if (!Common.Settings.Settings.instance().moduleSetting('network_log.preserve-log').get()) { this.reset(true); } } onPrimaryPageChanged(event) { const mainFrame = event.data.frame; const manager = mainFrame.resourceTreeModel().target().model(SDK.NetworkManager.NetworkManager); if (!manager || mainFrame.resourceTreeModel().target().parentTarget()?.type() === SDK.Target.Type.Frame) { return; } // If a page resulted in an error, the browser will navigate to an internal error page // hosted at 'chrome-error://...'. In this case, skip the frame navigated event to preserve // the network log. if (mainFrame.url !== mainFrame.unreachableUrl() && mainFrame.url.startsWith('chrome-error://')) { return; } const preserveLog = Common.Settings.Settings.instance().moduleSetting('network_log.preserve-log').get(); const oldRequests = this.requestsInternal; const oldManagerRequests = this.requestsInternal.filter(request => SDK.NetworkManager.NetworkManager.forRequest(request) === manager); const oldRequestsSet = this.requestsSet; this.requestsInternal = []; this.sentNetworkRequests = []; this.receivedNetworkResponses = []; this.requestsSet = new Set(); this.requestsMap.clear(); this.unresolvedPreflightRequests.clear(); this.dispatchEventToListeners(Events.Reset, { clearIfPreserved: !preserveLog }); // 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 SDK.PageLoad.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) { currentPageLoad?.bindRequest(request); oldRequestsSet.delete(request); this.addRequest(request); } if (preserveLog) { for (const request of oldRequestsSet) { this.addRequest(request); request.preserved = true; } } if (currentPageLoad) { this.pageLoadForManager.set(manager, currentPageLoad); } } addRequest(request) { this.requestsInternal.push(request); this.requestsSet.add(request); const requestList = this.requestsMap.get(request.requestId()); if (!requestList) { this.requestsMap.set(request.requestId(), [request]); } else { requestList.push(request); } this.tryResolvePreflightRequests(request); this.dispatchEventToListeners(Events.RequestAdded, request); } removeRequest(request) { const index = this.requestsInternal.indexOf(request); if (index > -1) { this.requestsInternal.splice(index, 1); } this.requestsSet.delete(request); this.requestsMap.delete(request.requestId()); this.dispatchEventToListeners(Events.RequestRemoved, request); } tryResolvePreflightRequests(request) { if (request.isPreflightRequest()) { const initiator = request.initiator(); if (initiator && initiator.requestId) { const [initiatorRequest] = this.requestsForId(initiator.requestId); if (initiatorRequest) { request.setPreflightInitiatorRequest(initiatorRequest); initiatorRequest.setPreflightRequest(request); } else { this.unresolvedPreflightRequests.set(initiator.requestId, request); } } } else { const preflightRequest = this.unresolvedPreflightRequests.get(request.requestId()); if (preflightRequest) { this.unresolvedPreflightRequests.delete(request.requestId()); request.setPreflightRequest(preflightRequest); preflightRequest.setPreflightInitiatorRequest(request); // Force recomputation of initiator info, if it already exists. const data = this.initiatorData.get(preflightRequest); if (data) { data.info = null; } this.dispatchEventToListeners(Events.RequestUpdated, preflightRequest); } } } importRequests(requests) { this.reset(true); this.requestsInternal = []; this.sentNetworkRequests = []; this.receivedNetworkResponses = []; this.requestsSet.clear(); this.requestsMap.clear(); this.unresolvedPreflightRequests.clear(); for (const request of requests) { this.addRequest(request); } } onRequestStarted(event) { const { request, originalRequest } = event.data; if (originalRequest) { this.sentNetworkRequests.push(originalRequest); } this.requestsSet.add(request); const manager = SDK.NetworkManager.NetworkManager.forRequest(request); const pageLoad = manager ? this.pageLoadForManager.get(manager) : null; if (pageLoad) { pageLoad.bindRequest(request); } this.addRequest(request); } onResponseReceived(event) { const response = event.data.response; this.receivedNetworkResponses.push(response); } onRequestUpdated(event) { const request = event.data; if (!this.requestsSet.has(request)) { return; } // This is only triggered in an edge case in which Chrome reports 2 preflight requests. The // first preflight gets aborted and should not be shown in DevTools. // (see https://crbug.com/1290390 for details) if (request.isPreflightRequest() && request.corsErrorStatus()?.corsError === "UnexpectedPrivateNetworkAccess" /* Protocol.Network.CorsError.UnexpectedPrivateNetworkAccess */) { this.removeRequest(request); return; } this.dispatchEventToListeners(Events.RequestUpdated, request); } onRequestRedirect(event) { this.initiatorData.delete(event.data); } onDOMContentLoaded(resourceTreeModel, event) { const networkManager = resourceTreeModel.target().model(SDK.NetworkManager.NetworkManager); const pageLoad = networkManager ? this.pageLoadForManager.get(networkManager) : null; if (pageLoad) { pageLoad.contentLoadTime = event.data; } } onLoad(event) { const networkManager = event.data.resourceTreeModel.target().model(SDK.NetworkManager.NetworkManager); const pageLoad = networkManager ? this.pageLoadForManager.get(networkManager) : null; if (pageLoad) { pageLoad.loadTime = event.data.loadTime; } } reset(clearIfPreserved) { this.requestsInternal = []; this.sentNetworkRequests = []; this.receivedNetworkResponses = []; this.requestsSet.clear(); this.requestsMap.clear(); this.unresolvedPreflightRequests.clear(); const managers = new Set(SDK.TargetManager.TargetManager.instance().models(SDK.NetworkManager.NetworkManager)); for (const manager of this.pageLoadForManager.keys()) { if (!managers.has(manager)) { this.pageLoadForManager.delete(manager); } } this.dispatchEventToListeners(Events.Reset, { clearIfPreserved }); } networkMessageGenerated(networkManager, event) { const { message, warning, requestId } = event.data; const consoleMessage = new SDK.ConsoleModel.ConsoleMessage(networkManager.target().model(SDK.RuntimeModel.RuntimeModel), "network" /* Protocol.Log.LogEntrySource.Network */, warning ? "warning" /* Protocol.Log.LogEntryLevel.Warning */ : "info" /* Protocol.Log.LogEntryLevel.Info */, message); this.associateConsoleMessageWithRequest(consoleMessage, requestId); networkManager.target().model(SDK.ConsoleModel.ConsoleModel)?.addMessage(consoleMessage); } associateConsoleMessageWithRequest(consoleMessage, requestId) { const target = consoleMessage.target(); const networkManager = target ? target.model(SDK.NetworkManager.NetworkManager) : null; if (!networkManager) { return; } const request = this.requestByManagerAndId(networkManager, requestId); if (!request) { return; } consoleMessageToRequest.set(consoleMessage, request); const initiator = request.initiator(); if (initiator) { consoleMessage.stackTrace = initiator.stack || undefined; if (initiator.url) { consoleMessage.url = initiator.url; consoleMessage.line = initiator.lineNumber || 0; } } } static requestForConsoleMessage(consoleMessage) { return consoleMessageToRequest.get(consoleMessage) || null; } requestsForId(requestId) { return this.requestsMap.get(requestId) || []; } } const consoleMessageToRequest = new WeakMap(); // TODO(crbug.com/1167717): Make this a const enum again // eslint-disable-next-line rulesdir/const_enum export var Events; (function (Events) { Events["Reset"] = "Reset"; Events["RequestAdded"] = "RequestAdded"; Events["RequestUpdated"] = "RequestUpdated"; Events["RequestRemoved"] = "RequestRemoved"; })(Events || (Events = {})); //# sourceMappingURL=NetworkLog.js.map