|  | /* | 
|  | * 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. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * @unrestricted | 
|  | */ | 
|  | export default class NetworkManager extends SDK.SDKModel { | 
|  | /** | 
|  | * @param {!SDK.Target} target | 
|  | */ | 
|  | constructor(target) { | 
|  | super(target); | 
|  | this._dispatcher = new NetworkDispatcher(this); | 
|  | this._networkAgent = target.networkAgent(); | 
|  | target.registerNetworkDispatcher(this._dispatcher); | 
|  | if (Common.moduleSetting('cacheDisabled').get()) { | 
|  | this._networkAgent.setCacheDisabled(true); | 
|  | } | 
|  |  | 
|  | this._networkAgent.enable(undefined, undefined, MAX_EAGER_POST_REQUEST_BODY_LENGTH); | 
|  |  | 
|  | this._bypassServiceWorkerSetting = Common.settings.createSetting('bypassServiceWorker', false); | 
|  | if (this._bypassServiceWorkerSetting.get()) { | 
|  | this._bypassServiceWorkerChanged(); | 
|  | } | 
|  | this._bypassServiceWorkerSetting.addChangeListener(this._bypassServiceWorkerChanged, this); | 
|  |  | 
|  | Common.moduleSetting('cacheDisabled').addChangeListener(this._cacheDisabledSettingChanged, this); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} request | 
|  | * @return {?NetworkManager} | 
|  | */ | 
|  | static forRequest(request) { | 
|  | return request[_networkManagerForRequestSymbol]; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} request | 
|  | * @return {boolean} | 
|  | */ | 
|  | static canReplayRequest(request) { | 
|  | return !!request[_networkManagerForRequestSymbol] && request.resourceType() === Common.resourceTypes.XHR; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} request | 
|  | */ | 
|  | static replayRequest(request) { | 
|  | const manager = request[_networkManagerForRequestSymbol]; | 
|  | if (!manager) { | 
|  | return; | 
|  | } | 
|  | manager._networkAgent.replayXHR(request.requestId()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} request | 
|  | * @param {string} query | 
|  | * @param {boolean} caseSensitive | 
|  | * @param {boolean} isRegex | 
|  | * @return {!Promise<!Array<!Common.ContentProvider.SearchMatch>>} | 
|  | */ | 
|  | static async searchInRequest(request, query, caseSensitive, isRegex) { | 
|  | const manager = NetworkManager.forRequest(request); | 
|  | if (!manager) { | 
|  | return []; | 
|  | } | 
|  | const response = await manager._networkAgent.invoke_searchInResponseBody( | 
|  | {requestId: request.requestId(), query: query, caseSensitive: caseSensitive, isRegex: isRegex}); | 
|  | return response.result || []; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} request | 
|  | * @return {!Promise<!SDK.NetworkRequest.ContentData>} | 
|  | */ | 
|  | static async requestContentData(request) { | 
|  | if (request.resourceType() === Common.resourceTypes.WebSocket) { | 
|  | return {error: 'Content for WebSockets is currently not supported', content: null, encoded: false}; | 
|  | } | 
|  | if (!request.finished) { | 
|  | await request.once(SDK.NetworkRequest.Events.FinishedLoading); | 
|  | } | 
|  | const manager = NetworkManager.forRequest(request); | 
|  | if (!manager) { | 
|  | return {error: 'No network manager for request', content: null, encoded: false}; | 
|  | } | 
|  | const response = await manager._networkAgent.invoke_getResponseBody({requestId: request.requestId()}); | 
|  | const error = response[Protocol.Error] || null; | 
|  | return {error: error, content: error ? null : response.body, encoded: response.base64Encoded}; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} request | 
|  | * @return {!Promise<?string>} | 
|  | */ | 
|  | static requestPostData(request) { | 
|  | const manager = NetworkManager.forRequest(request); | 
|  | if (manager) { | 
|  | return manager._networkAgent.getRequestPostData(request.backendRequestId()); | 
|  | } | 
|  | console.error('No network manager for request'); | 
|  | return /** @type {!Promise<?string>} */ (Promise.resolve(null)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkManager.Conditions} conditions | 
|  | * @return {!Protocol.Network.ConnectionType} | 
|  | * TODO(allada): this belongs to NetworkConditionsSelector, which should hardcode/guess it. | 
|  | */ | 
|  | static _connectionType(conditions) { | 
|  | if (!conditions.download && !conditions.upload) { | 
|  | return Protocol.Network.ConnectionType.None; | 
|  | } | 
|  | let types = NetworkManager._connectionTypes; | 
|  | if (!types) { | 
|  | NetworkManager._connectionTypes = []; | 
|  | types = NetworkManager._connectionTypes; | 
|  | types.push(['2g', Protocol.Network.ConnectionType.Cellular2g]); | 
|  | types.push(['3g', Protocol.Network.ConnectionType.Cellular3g]); | 
|  | types.push(['4g', Protocol.Network.ConnectionType.Cellular4g]); | 
|  | types.push(['bluetooth', Protocol.Network.ConnectionType.Bluetooth]); | 
|  | types.push(['wifi', Protocol.Network.ConnectionType.Wifi]); | 
|  | types.push(['wimax', Protocol.Network.ConnectionType.Wimax]); | 
|  | } | 
|  | for (const type of types) { | 
|  | if (conditions.title.toLowerCase().indexOf(type[0]) !== -1) { | 
|  | return type[1]; | 
|  | } | 
|  | } | 
|  | return Protocol.Network.ConnectionType.Other; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Object} headers | 
|  | * @return {!Object<string, string>} | 
|  | */ | 
|  | static lowercaseHeaders(headers) { | 
|  | const newHeaders = {}; | 
|  | for (const headerName in headers) { | 
|  | newHeaders[headerName.toLowerCase()] = headers[headerName]; | 
|  | } | 
|  | return newHeaders; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} url | 
|  | * @return {!SDK.NetworkRequest} | 
|  | */ | 
|  | inflightRequestForURL(url) { | 
|  | return this._dispatcher._inflightRequestsByURL[url]; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Common.Event} event | 
|  | */ | 
|  | _cacheDisabledSettingChanged(event) { | 
|  | const enabled = /** @type {boolean} */ (event.data); | 
|  | this._networkAgent.setCacheDisabled(enabled); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | */ | 
|  | dispose() { | 
|  | Common.moduleSetting('cacheDisabled').removeChangeListener(this._cacheDisabledSettingChanged, this); | 
|  | } | 
|  |  | 
|  | _bypassServiceWorkerChanged() { | 
|  | this._networkAgent.setBypassServiceWorker(this._bypassServiceWorkerSetting.get()); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @enum {symbol} */ | 
|  | export const Events = { | 
|  | RequestStarted: Symbol('RequestStarted'), | 
|  | RequestUpdated: Symbol('RequestUpdated'), | 
|  | RequestFinished: Symbol('RequestFinished'), | 
|  | RequestUpdateDropped: Symbol('RequestUpdateDropped'), | 
|  | ResponseReceived: Symbol('ResponseReceived'), | 
|  | MessageGenerated: Symbol('MessageGenerated'), | 
|  | RequestRedirected: Symbol('RequestRedirected'), | 
|  | LoadingFinished: Symbol('LoadingFinished'), | 
|  | }; | 
|  |  | 
|  | const _MIMETypes = { | 
|  | 'text/html': {'document': true}, | 
|  | 'text/xml': {'document': true}, | 
|  | 'text/plain': {'document': true}, | 
|  | 'application/xhtml+xml': {'document': true}, | 
|  | 'image/svg+xml': {'document': true}, | 
|  | 'text/css': {'stylesheet': true}, | 
|  | 'text/xsl': {'stylesheet': true}, | 
|  | 'text/vtt': {'texttrack': true}, | 
|  | 'application/pdf': {'document': true}, | 
|  | }; | 
|  |  | 
|  | /** @type {!SDK.NetworkManager.Conditions} */ | 
|  | export const NoThrottlingConditions = { | 
|  | title: ls`Online`, | 
|  | download: -1, | 
|  | upload: -1, | 
|  | latency: 0 | 
|  | }; | 
|  |  | 
|  | /** @type {!SDK.NetworkManager.Conditions} */ | 
|  | export const OfflineConditions = { | 
|  | title: Common.UIString('Offline'), | 
|  | download: 0, | 
|  | upload: 0, | 
|  | latency: 0, | 
|  | }; | 
|  |  | 
|  | /** @type {!SDK.NetworkManager.Conditions} */ | 
|  | export const Slow3GConditions = { | 
|  | title: Common.UIString('Slow 3G'), | 
|  | download: 500 * 1024 / 8 * .8, | 
|  | upload: 500 * 1024 / 8 * .8, | 
|  | latency: 400 * 5, | 
|  | }; | 
|  |  | 
|  | /** @type {!SDK.NetworkManager.Conditions} */ | 
|  | export const Fast3GConditions = { | 
|  | title: Common.UIString('Fast 3G'), | 
|  | download: 1.6 * 1024 * 1024 / 8 * .9, | 
|  | upload: 750 * 1024 / 8 * .9, | 
|  | latency: 150 * 3.75, | 
|  | }; | 
|  |  | 
|  | const _networkManagerForRequestSymbol = Symbol('NetworkManager'); | 
|  | const MAX_EAGER_POST_REQUEST_BODY_LENGTH = 64 * 1024;  // bytes | 
|  |  | 
|  | /** | 
|  | * @implements {Protocol.NetworkDispatcher} | 
|  | * @unrestricted | 
|  | */ | 
|  | export class NetworkDispatcher { | 
|  | /** | 
|  | * @param {!NetworkManager} manager | 
|  | */ | 
|  | constructor(manager) { | 
|  | this._manager = manager; | 
|  | /** @type {!Object<!Protocol.Network.RequestId, !SDK.NetworkRequest>} */ | 
|  | this._inflightRequestsById = {}; | 
|  | /** @type {!Object<string, !SDK.NetworkRequest>} */ | 
|  | this._inflightRequestsByURL = {}; | 
|  | /** @type {!Map<string, !RedirectExtraInfoBuilder>} */ | 
|  | this._requestIdToRedirectExtraInfoBuilder = new Map(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Protocol.Network.Headers} headersMap | 
|  | * @return {!Array.<!SDK.NetworkRequest.NameValue>} | 
|  | */ | 
|  | _headersMapToHeadersArray(headersMap) { | 
|  | const result = []; | 
|  | for (const name in headersMap) { | 
|  | const values = headersMap[name].split('\n'); | 
|  | for (let i = 0; i < values.length; ++i) { | 
|  | result.push({name: name, value: values[i]}); | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} networkRequest | 
|  | * @param {!Protocol.Network.Request} request | 
|  | */ | 
|  | _updateNetworkRequestWithRequest(networkRequest, request) { | 
|  | networkRequest.requestMethod = request.method; | 
|  | networkRequest.setRequestHeaders(this._headersMapToHeadersArray(request.headers)); | 
|  | networkRequest.setRequestFormData(!!request.hasPostData, request.postData || null); | 
|  | networkRequest.setInitialPriority(request.initialPriority); | 
|  | networkRequest.mixedContentType = request.mixedContentType || Protocol.Security.MixedContentType.None; | 
|  | networkRequest.setReferrerPolicy(request.referrerPolicy); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} networkRequest | 
|  | * @param {!Protocol.Network.Response=} response | 
|  | */ | 
|  | _updateNetworkRequestWithResponse(networkRequest, response) { | 
|  | if (response.url && networkRequest.url() !== response.url) { | 
|  | networkRequest.setUrl(response.url); | 
|  | } | 
|  | networkRequest.mimeType = response.mimeType; | 
|  | networkRequest.statusCode = response.status; | 
|  | networkRequest.statusText = response.statusText; | 
|  | if (!networkRequest.hasExtraResponseInfo()) { | 
|  | networkRequest.responseHeaders = this._headersMapToHeadersArray(response.headers); | 
|  | } | 
|  |  | 
|  | if (response.encodedDataLength >= 0) { | 
|  | networkRequest.setTransferSize(response.encodedDataLength); | 
|  | } | 
|  |  | 
|  | if (response.requestHeaders && !networkRequest.hasExtraRequestInfo()) { | 
|  | // TODO(http://crbug.com/1004979): Stop using response.requestHeaders and | 
|  | //   response.requestHeadersText once shared workers | 
|  | //   emit Network.*ExtraInfo events for their network requests. | 
|  | networkRequest.setRequestHeaders(this._headersMapToHeadersArray(response.requestHeaders)); | 
|  | networkRequest.setRequestHeadersText(response.requestHeadersText || ''); | 
|  | } | 
|  |  | 
|  | networkRequest.connectionReused = response.connectionReused; | 
|  | networkRequest.connectionId = String(response.connectionId); | 
|  | if (response.remoteIPAddress) { | 
|  | networkRequest.setRemoteAddress(response.remoteIPAddress, response.remotePort || -1); | 
|  | } | 
|  |  | 
|  | if (response.fromServiceWorker) { | 
|  | networkRequest.fetchedViaServiceWorker = true; | 
|  | } | 
|  |  | 
|  | if (response.fromDiskCache) { | 
|  | networkRequest.setFromDiskCache(); | 
|  | } | 
|  |  | 
|  | if (response.fromPrefetchCache) { | 
|  | networkRequest.setFromPrefetchCache(); | 
|  | } | 
|  |  | 
|  | networkRequest.timing = response.timing; | 
|  |  | 
|  | networkRequest.protocol = response.protocol || ''; | 
|  |  | 
|  | networkRequest.setSecurityState(response.securityState); | 
|  |  | 
|  | if (!this._mimeTypeIsConsistentWithType(networkRequest)) { | 
|  | const message = Common.UIString( | 
|  | 'Resource interpreted as %s but transferred with MIME type %s: "%s".', networkRequest.resourceType().title(), | 
|  | networkRequest.mimeType, networkRequest.url()); | 
|  | this._manager.dispatchEventToListeners( | 
|  | Events.MessageGenerated, {message: message, requestId: networkRequest.requestId(), warning: true}); | 
|  | } | 
|  |  | 
|  | if (response.securityDetails) { | 
|  | networkRequest.setSecurityDetails(response.securityDetails); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} networkRequest | 
|  | * @return {boolean} | 
|  | */ | 
|  | _mimeTypeIsConsistentWithType(networkRequest) { | 
|  | // If status is an error, content is likely to be of an inconsistent type, | 
|  | // as it's going to be an error message. We do not want to emit a warning | 
|  | // for this, though, as this will already be reported as resource loading failure. | 
|  | // Also, if a URL like http://localhost/wiki/load.php?debug=true&lang=en produces text/css and gets reloaded, | 
|  | // it is 304 Not Modified and its guessed mime-type is text/php, which is wrong. | 
|  | // Don't check for mime-types in 304-resources. | 
|  | if (networkRequest.hasErrorStatusCode() || networkRequest.statusCode === 304 || networkRequest.statusCode === 204) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const resourceType = networkRequest.resourceType(); | 
|  | if (resourceType !== Common.resourceTypes.Stylesheet && resourceType !== Common.resourceTypes.Document && | 
|  | resourceType !== Common.resourceTypes.TextTrack) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  |  | 
|  | if (!networkRequest.mimeType) { | 
|  | return true; | 
|  | }  // Might be not known for cached resources with null responses. | 
|  |  | 
|  | if (networkRequest.mimeType in _MIMETypes) { | 
|  | return resourceType.name() in _MIMETypes[networkRequest.mimeType]; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.ResourcePriority} newPriority | 
|  | * @param {!Protocol.Network.MonotonicTime} timestamp | 
|  | */ | 
|  | resourceChangedPriority(requestId, newPriority, timestamp) { | 
|  | const networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (networkRequest) { | 
|  | networkRequest.setPriority(newPriority); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.SignedExchangeInfo} info | 
|  | */ | 
|  | signedExchangeReceived(requestId, info) { | 
|  | // While loading a signed exchange, a signedExchangeReceived event is sent | 
|  | // between two requestWillBeSent events. | 
|  | // 1. The first requestWillBeSent is sent while starting the navigation (or | 
|  | //    prefetching). | 
|  | // 2. This signedExchangeReceived event is sent when the browser detects the | 
|  | //    signed exchange. | 
|  | // 3. The second requestWillBeSent is sent with the generated redirect | 
|  | //    response and a new redirected request which URL is the inner request | 
|  | //    URL of the signed exchange. | 
|  | let networkRequest = this._inflightRequestsById[requestId]; | 
|  | // |requestId| is available only for navigation requests. If the request was | 
|  | // sent from a renderer process for prefetching, it is not available. In the | 
|  | // case, need to fallback to look for the URL. | 
|  | // TODO(crbug/841076): Sends the request ID of prefetching to the browser | 
|  | // process and DevTools to find the matching request. | 
|  | if (!networkRequest) { | 
|  | networkRequest = this._inflightRequestsByURL[info.outerResponse.url]; | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  | } | 
|  | networkRequest.setSignedExchangeInfo(info); | 
|  | networkRequest.setResourceType(Common.resourceTypes.SignedExchange); | 
|  |  | 
|  | this._updateNetworkRequestWithResponse(networkRequest, info.outerResponse); | 
|  | this._updateNetworkRequest(networkRequest); | 
|  | this._manager.dispatchEventToListeners(Events.ResponseReceived, networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.LoaderId} loaderId | 
|  | * @param {string} documentURL | 
|  | * @param {!Protocol.Network.Request} request | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | * @param {!Protocol.Network.TimeSinceEpoch} wallTime | 
|  | * @param {!Protocol.Network.Initiator} initiator | 
|  | * @param {!Protocol.Network.Response=} redirectResponse | 
|  | * @param {!Protocol.Network.ResourceType=} resourceType | 
|  | * @param {!Protocol.Page.FrameId=} frameId | 
|  | */ | 
|  | requestWillBeSent( | 
|  | requestId, loaderId, documentURL, request, time, wallTime, initiator, redirectResponse, resourceType, frameId) { | 
|  | let networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (networkRequest) { | 
|  | // FIXME: move this check to the backend. | 
|  | if (!redirectResponse) { | 
|  | return; | 
|  | } | 
|  | // If signedExchangeReceived event has already been sent for the request, | 
|  | // ignores the internally generated |redirectResponse|. The | 
|  | // |outerResponse| of SignedExchangeInfo was set to |networkRequest| in | 
|  | // signedExchangeReceived(). | 
|  | if (!networkRequest.signedExchangeInfo()) { | 
|  | this.responseReceived( | 
|  | requestId, loaderId, time, Protocol.Network.ResourceType.Other, redirectResponse, frameId); | 
|  | } | 
|  | networkRequest = this._appendRedirect(requestId, time, request.url); | 
|  | this._manager.dispatchEventToListeners(Events.RequestRedirected, networkRequest); | 
|  | } else { | 
|  | networkRequest = | 
|  | this._createNetworkRequest(requestId, frameId || '', loaderId, request.url, documentURL, initiator); | 
|  | } | 
|  | networkRequest.hasNetworkData = true; | 
|  | this._updateNetworkRequestWithRequest(networkRequest, request); | 
|  | networkRequest.setIssueTime(time, wallTime); | 
|  | networkRequest.setResourceType( | 
|  | resourceType ? Common.resourceTypes[resourceType] : Protocol.Network.ResourceType.Other); | 
|  |  | 
|  | this._getExtraInfoBuilder(requestId).addRequest(networkRequest); | 
|  |  | 
|  | this._startNetworkRequest(networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | */ | 
|  | requestServedFromCache(requestId) { | 
|  | const networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | networkRequest.setFromMemoryCache(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.LoaderId} loaderId | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | * @param {!Protocol.Network.ResourceType} resourceType | 
|  | * @param {!Protocol.Network.Response} response | 
|  | * @param {!Protocol.Page.FrameId=} frameId | 
|  | */ | 
|  | responseReceived(requestId, loaderId, time, resourceType, response, frameId) { | 
|  | const networkRequest = this._inflightRequestsById[requestId]; | 
|  | const lowercaseHeaders = NetworkManager.lowercaseHeaders(response.headers); | 
|  | if (!networkRequest) { | 
|  | // We missed the requestWillBeSent. | 
|  | const eventData = {}; | 
|  | eventData.url = response.url; | 
|  | eventData.frameId = frameId || ''; | 
|  | eventData.loaderId = loaderId; | 
|  | eventData.resourceType = resourceType; | 
|  | eventData.mimeType = response.mimeType; | 
|  | const lastModifiedHeader = lowercaseHeaders['last-modified']; | 
|  | eventData.lastModified = lastModifiedHeader ? new Date(lastModifiedHeader) : null; | 
|  | this._manager.dispatchEventToListeners(Events.RequestUpdateDropped, eventData); | 
|  | return; | 
|  | } | 
|  |  | 
|  | networkRequest.responseReceivedTime = time; | 
|  | networkRequest.setResourceType(Common.resourceTypes[resourceType]); | 
|  |  | 
|  | // net::ParsedCookie::kMaxCookieSize = 4096 (net/cookies/parsed_cookie.h) | 
|  | if ('set-cookie' in lowercaseHeaders && lowercaseHeaders['set-cookie'].length > 4096) { | 
|  | const values = lowercaseHeaders['set-cookie'].split('\n'); | 
|  | for (let i = 0; i < values.length; ++i) { | 
|  | if (values[i].length <= 4096) { | 
|  | continue; | 
|  | } | 
|  | const message = Common.UIString( | 
|  | 'Set-Cookie header is ignored in response from url: %s. Cookie length should be less than or equal to 4096 characters.', | 
|  | response.url); | 
|  | this._manager.dispatchEventToListeners( | 
|  | Events.MessageGenerated, {message: message, requestId: requestId, warning: true}); | 
|  | } | 
|  | } | 
|  |  | 
|  | this._updateNetworkRequestWithResponse(networkRequest, response); | 
|  |  | 
|  | this._updateNetworkRequest(networkRequest); | 
|  | this._manager.dispatchEventToListeners(Events.ResponseReceived, networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | * @param {number} dataLength | 
|  | * @param {number} encodedDataLength | 
|  | */ | 
|  | dataReceived(requestId, time, dataLength, encodedDataLength) { | 
|  | let networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (!networkRequest) { | 
|  | networkRequest = this._maybeAdoptMainResourceRequest(requestId); | 
|  | } | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | networkRequest.resourceSize += dataLength; | 
|  | if (encodedDataLength !== -1) { | 
|  | networkRequest.increaseTransferSize(encodedDataLength); | 
|  | } | 
|  | networkRequest.endTime = time; | 
|  |  | 
|  | this._updateNetworkRequest(networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.MonotonicTime} finishTime | 
|  | * @param {number} encodedDataLength | 
|  | * @param {boolean=} shouldReportCorbBlocking | 
|  | */ | 
|  | loadingFinished(requestId, finishTime, encodedDataLength, shouldReportCorbBlocking) { | 
|  | let networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (!networkRequest) { | 
|  | networkRequest = this._maybeAdoptMainResourceRequest(requestId); | 
|  | } | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  | this._getExtraInfoBuilder(requestId).finished(); | 
|  | this._finishNetworkRequest(networkRequest, finishTime, encodedDataLength, shouldReportCorbBlocking); | 
|  | this._manager.dispatchEventToListeners(Events.LoadingFinished, networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | * @param {!Protocol.Network.ResourceType} resourceType | 
|  | * @param {string} localizedDescription | 
|  | * @param {boolean=} canceled | 
|  | * @param {!Protocol.Network.BlockedReason=} blockedReason | 
|  | */ | 
|  | loadingFailed(requestId, time, resourceType, localizedDescription, canceled, blockedReason) { | 
|  | const networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | networkRequest.failed = true; | 
|  | networkRequest.setResourceType(Common.resourceTypes[resourceType]); | 
|  | networkRequest.canceled = !!canceled; | 
|  | if (blockedReason) { | 
|  | networkRequest.setBlockedReason(blockedReason); | 
|  | if (blockedReason === Protocol.Network.BlockedReason.Inspector) { | 
|  | const message = Common.UIString('Request was blocked by DevTools: "%s".', networkRequest.url()); | 
|  | this._manager.dispatchEventToListeners( | 
|  | Events.MessageGenerated, {message: message, requestId: requestId, warning: true}); | 
|  | } | 
|  | } | 
|  | networkRequest.localizedFailDescription = localizedDescription; | 
|  | this._getExtraInfoBuilder(requestId).finished(); | 
|  | this._finishNetworkRequest(networkRequest, time, -1); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {string} requestURL | 
|  | * @param {!Protocol.Network.Initiator=} initiator | 
|  | */ | 
|  | webSocketCreated(requestId, requestURL, initiator) { | 
|  | const networkRequest = new SDK.NetworkRequest(requestId, requestURL, '', '', '', initiator || null); | 
|  | networkRequest[_networkManagerForRequestSymbol] = this._manager; | 
|  | networkRequest.setResourceType(Common.resourceTypes.WebSocket); | 
|  | this._startNetworkRequest(networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | * @param {!Protocol.Network.TimeSinceEpoch} wallTime | 
|  | * @param {!Protocol.Network.WebSocketRequest} request | 
|  | */ | 
|  | webSocketWillSendHandshakeRequest(requestId, time, wallTime, request) { | 
|  | const networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | networkRequest.requestMethod = 'GET'; | 
|  | networkRequest.setRequestHeaders(this._headersMapToHeadersArray(request.headers)); | 
|  | networkRequest.setIssueTime(time, wallTime); | 
|  |  | 
|  | this._updateNetworkRequest(networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | * @param {!Protocol.Network.WebSocketResponse} response | 
|  | */ | 
|  | webSocketHandshakeResponseReceived(requestId, time, response) { | 
|  | const networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | networkRequest.statusCode = response.status; | 
|  | networkRequest.statusText = response.statusText; | 
|  | networkRequest.responseHeaders = this._headersMapToHeadersArray(response.headers); | 
|  | networkRequest.responseHeadersText = response.headersText || ''; | 
|  | if (response.requestHeaders) { | 
|  | networkRequest.setRequestHeaders(this._headersMapToHeadersArray(response.requestHeaders)); | 
|  | } | 
|  | if (response.requestHeadersText) { | 
|  | networkRequest.setRequestHeadersText(response.requestHeadersText); | 
|  | } | 
|  | networkRequest.responseReceivedTime = time; | 
|  | networkRequest.protocol = 'websocket'; | 
|  |  | 
|  | this._updateNetworkRequest(networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | * @param {!Protocol.Network.WebSocketFrame} response | 
|  | */ | 
|  | webSocketFrameReceived(requestId, time, response) { | 
|  | const networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | networkRequest.addProtocolFrame(response, time, false); | 
|  | networkRequest.responseReceivedTime = time; | 
|  |  | 
|  | this._updateNetworkRequest(networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | * @param {!Protocol.Network.WebSocketFrame} response | 
|  | */ | 
|  | webSocketFrameSent(requestId, time, response) { | 
|  | const networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | networkRequest.addProtocolFrame(response, time, true); | 
|  | networkRequest.responseReceivedTime = time; | 
|  |  | 
|  | this._updateNetworkRequest(networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | * @param {string} errorMessage | 
|  | */ | 
|  | webSocketFrameError(requestId, time, errorMessage) { | 
|  | const networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | networkRequest.addProtocolFrameError(errorMessage, time); | 
|  | networkRequest.responseReceivedTime = time; | 
|  |  | 
|  | this._updateNetworkRequest(networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | */ | 
|  | webSocketClosed(requestId, time) { | 
|  | const networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  | this._finishNetworkRequest(networkRequest, time, -1); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | * @param {string} eventName | 
|  | * @param {string} eventId | 
|  | * @param {string} data | 
|  | */ | 
|  | eventSourceMessageReceived(requestId, time, eventName, eventId, data) { | 
|  | const networkRequest = this._inflightRequestsById[requestId]; | 
|  | if (!networkRequest) { | 
|  | return; | 
|  | } | 
|  | networkRequest.addEventSourceMessage(time, eventName, eventId, data); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.InterceptionId} interceptionId | 
|  | * @param {!Protocol.Network.Request} request | 
|  | * @param {!Protocol.Page.FrameId} frameId | 
|  | * @param {!Protocol.Network.ResourceType} resourceType | 
|  | * @param {boolean} isNavigationRequest | 
|  | * @param {boolean=} isDownload | 
|  | * @param {string=} redirectUrl | 
|  | * @param {!Protocol.Network.AuthChallenge=} authChallenge | 
|  | * @param {!Protocol.Network.ErrorReason=} responseErrorReason | 
|  | * @param {number=} responseStatusCode | 
|  | * @param {!Protocol.Network.Headers=} responseHeaders | 
|  | * @param {!Protocol.Network.RequestId=} requestId | 
|  | */ | 
|  | requestIntercepted( | 
|  | interceptionId, request, frameId, resourceType, isNavigationRequest, isDownload, redirectUrl, authChallenge, | 
|  | responseErrorReason, responseStatusCode, responseHeaders, requestId) { | 
|  | SDK.multitargetNetworkManager._requestIntercepted(new InterceptedRequest( | 
|  | this._manager.target().networkAgent(), interceptionId, request, frameId, resourceType, isNavigationRequest, | 
|  | isDownload, redirectUrl, authChallenge, responseErrorReason, responseStatusCode, responseHeaders, requestId)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Array<!Protocol.Network.BlockedCookieWithReason>} blockedCookies | 
|  | * @param {!Protocol.Network.Headers} headers | 
|  | */ | 
|  | requestWillBeSentExtraInfo(requestId, blockedCookies, headers) { | 
|  | /** @type {!SDK.NetworkRequest.ExtraRequestInfo} */ | 
|  | const extraRequestInfo = { | 
|  | blockedRequestCookies: blockedCookies.map(blockedCookie => { | 
|  | return { | 
|  | blockedReasons: blockedCookie.blockedReasons, | 
|  | cookie: SDK.Cookie.fromProtocolCookie(blockedCookie.cookie) | 
|  | }; | 
|  | }), | 
|  | requestHeaders: this._headersMapToHeadersArray(headers) | 
|  | }; | 
|  | this._getExtraInfoBuilder(requestId).addRequestExtraInfo(extraRequestInfo); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Array<!Protocol.Network.BlockedSetCookieWithReason>} blockedCookies | 
|  | * @param {!Protocol.Network.Headers} headers | 
|  | * @param {string=} headersText | 
|  | */ | 
|  | responseReceivedExtraInfo(requestId, blockedCookies, headers, headersText) { | 
|  | /** @type {!SDK.NetworkRequest.ExtraResponseInfo} */ | 
|  | const extraResponseInfo = { | 
|  | blockedResponseCookies: blockedCookies.map(blockedCookie => { | 
|  | return { | 
|  | blockedReasons: blockedCookie.blockedReasons, | 
|  | cookieLine: blockedCookie.cookieLine, | 
|  | cookie: blockedCookie.cookie ? SDK.Cookie.fromProtocolCookie(blockedCookie.cookie) : null | 
|  | }; | 
|  | }), | 
|  | responseHeaders: this._headersMapToHeadersArray(headers), | 
|  | responseHeadersText: headersText | 
|  | }; | 
|  | this._getExtraInfoBuilder(requestId).addResponseExtraInfo(extraResponseInfo); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @unrestricted | 
|  | * @param {boolean} isServiceWorker | 
|  | * @param {string} url | 
|  | * @param {string} firstPartyUrl | 
|  | * @param {!Array<!Protocol.Network.BlockedCookieWithReason>} blockedCookies | 
|  | */ | 
|  | cookiesBlocked(isServiceWorker, url, firstPartyUrl, blockedCookies) { | 
|  | // TODO(chromium:1032063): Implement this protocol message handler. | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} requestId | 
|  | * @return {!RedirectExtraInfoBuilder} | 
|  | */ | 
|  | _getExtraInfoBuilder(requestId) { | 
|  | if (!this._requestIdToRedirectExtraInfoBuilder.get(requestId)) { | 
|  | const deleteCallback = () => { | 
|  | this._requestIdToRedirectExtraInfoBuilder.delete(requestId); | 
|  | }; | 
|  | this._requestIdToRedirectExtraInfoBuilder.set(requestId, new RedirectExtraInfoBuilder(deleteCallback)); | 
|  | } | 
|  | return this._requestIdToRedirectExtraInfoBuilder.get(requestId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {!Protocol.Network.MonotonicTime} time | 
|  | * @param {string} redirectURL | 
|  | * @return {!SDK.NetworkRequest} | 
|  | */ | 
|  | _appendRedirect(requestId, time, redirectURL) { | 
|  | const originalNetworkRequest = this._inflightRequestsById[requestId]; | 
|  | let redirectCount = 0; | 
|  | for (let redirect = originalNetworkRequest.redirectSource(); redirect; redirect = redirect.redirectSource()) { | 
|  | redirectCount++; | 
|  | } | 
|  |  | 
|  | originalNetworkRequest.markAsRedirect(redirectCount); | 
|  | this._finishNetworkRequest(originalNetworkRequest, time, -1); | 
|  | const newNetworkRequest = this._createNetworkRequest( | 
|  | requestId, originalNetworkRequest.frameId, originalNetworkRequest.loaderId, redirectURL, | 
|  | originalNetworkRequest.documentURL, originalNetworkRequest.initiator()); | 
|  | newNetworkRequest.setRedirectSource(originalNetworkRequest); | 
|  | originalNetworkRequest.setRedirectDestination(newNetworkRequest); | 
|  | return newNetworkRequest; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} requestId | 
|  | * @return {?SDK.NetworkRequest} | 
|  | */ | 
|  | _maybeAdoptMainResourceRequest(requestId) { | 
|  | const request = SDK.multitargetNetworkManager._inflightMainResourceRequests.get(requestId); | 
|  | if (!request) { | 
|  | return null; | 
|  | } | 
|  | const oldDispatcher = NetworkManager.forRequest(request)._dispatcher; | 
|  | delete oldDispatcher._inflightRequestsById[requestId]; | 
|  | delete oldDispatcher._inflightRequestsByURL[request.url()]; | 
|  | this._inflightRequestsById[requestId] = request; | 
|  | this._inflightRequestsByURL[request.url()] = request; | 
|  | request[_networkManagerForRequestSymbol] = this._manager; | 
|  | return request; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} networkRequest | 
|  | */ | 
|  | _startNetworkRequest(networkRequest) { | 
|  | this._inflightRequestsById[networkRequest.requestId()] = networkRequest; | 
|  | this._inflightRequestsByURL[networkRequest.url()] = networkRequest; | 
|  | // The following relies on the fact that loaderIds and requestIds are | 
|  | // globally unique and that the main request has them equal. | 
|  | if (networkRequest.loaderId === networkRequest.requestId()) { | 
|  | SDK.multitargetNetworkManager._inflightMainResourceRequests.set(networkRequest.requestId(), networkRequest); | 
|  | } | 
|  |  | 
|  | this._manager.dispatchEventToListeners(Events.RequestStarted, networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} networkRequest | 
|  | */ | 
|  | _updateNetworkRequest(networkRequest) { | 
|  | this._manager.dispatchEventToListeners(Events.RequestUpdated, networkRequest); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} networkRequest | 
|  | * @param {!Protocol.Network.MonotonicTime} finishTime | 
|  | * @param {number} encodedDataLength | 
|  | * @param {boolean=} shouldReportCorbBlocking | 
|  | */ | 
|  | _finishNetworkRequest(networkRequest, finishTime, encodedDataLength, shouldReportCorbBlocking) { | 
|  | networkRequest.endTime = finishTime; | 
|  | networkRequest.finished = true; | 
|  | if (encodedDataLength >= 0) { | 
|  | const redirectSource = networkRequest.redirectSource(); | 
|  | if (redirectSource && redirectSource.signedExchangeInfo()) { | 
|  | networkRequest.setTransferSize(0); | 
|  | redirectSource.setTransferSize(encodedDataLength); | 
|  | this._updateNetworkRequest(redirectSource); | 
|  | } else { | 
|  | networkRequest.setTransferSize(encodedDataLength); | 
|  | } | 
|  | } | 
|  | this._manager.dispatchEventToListeners(Events.RequestFinished, networkRequest); | 
|  | delete this._inflightRequestsById[networkRequest.requestId()]; | 
|  | delete this._inflightRequestsByURL[networkRequest.url()]; | 
|  | SDK.multitargetNetworkManager._inflightMainResourceRequests.delete(networkRequest.requestId()); | 
|  |  | 
|  | if (shouldReportCorbBlocking) { | 
|  | const message = Common.UIString( | 
|  | `Cross-Origin Read Blocking (CORB) blocked cross-origin response %s with MIME type %s. See https://www.chromestatus.com/feature/5629709824032768 for more details.`, | 
|  | networkRequest.url(), networkRequest.mimeType); | 
|  | this._manager.dispatchEventToListeners( | 
|  | Events.MessageGenerated, {message: message, requestId: networkRequest.requestId(), warning: true}); | 
|  | } | 
|  |  | 
|  | if (Common.moduleSetting('monitoringXHREnabled').get() && | 
|  | networkRequest.resourceType().category() === Common.resourceCategories.XHR) { | 
|  | let message; | 
|  | const failedToLoad = networkRequest.failed || networkRequest.hasErrorStatusCode(); | 
|  | if (failedToLoad) { | 
|  | message = Common.UIString( | 
|  | '%s failed loading: %s "%s".', networkRequest.resourceType().title(), networkRequest.requestMethod, | 
|  | networkRequest.url()); | 
|  | } else { | 
|  | message = Common.UIString( | 
|  | '%s finished loading: %s "%s".', networkRequest.resourceType().title(), networkRequest.requestMethod, | 
|  | networkRequest.url()); | 
|  | } | 
|  |  | 
|  | this._manager.dispatchEventToListeners( | 
|  | Events.MessageGenerated, {message: message, requestId: networkRequest.requestId(), warning: false}); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Protocol.Network.RequestId} requestId | 
|  | * @param {string} frameId | 
|  | * @param {!Protocol.Network.LoaderId} loaderId | 
|  | * @param {string} url | 
|  | * @param {string} documentURL | 
|  | * @param {?Protocol.Network.Initiator} initiator | 
|  | */ | 
|  | _createNetworkRequest(requestId, frameId, loaderId, url, documentURL, initiator) { | 
|  | const request = new SDK.NetworkRequest(requestId, url, documentURL, frameId, loaderId, initiator); | 
|  | request[_networkManagerForRequestSymbol] = this._manager; | 
|  | return request; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @implements {SDK.SDKModelObserver<!NetworkManager>} | 
|  | * @unrestricted | 
|  | */ | 
|  | export class MultitargetNetworkManager extends Common.Object { | 
|  | constructor() { | 
|  | super(); | 
|  | this._userAgentOverride = ''; | 
|  | /** @type {!Set<!Protocol.NetworkAgent>} */ | 
|  | this._agents = new Set(); | 
|  | /** @type {!Map<string, !SDK.NetworkRequest>} */ | 
|  | this._inflightMainResourceRequests = new Map(); | 
|  | /** @type {!SDK.NetworkManager.Conditions} */ | 
|  | this._networkConditions = NoThrottlingConditions; | 
|  | /** @type {?Promise} */ | 
|  | this._updatingInterceptionPatternsPromise = null; | 
|  |  | 
|  | // TODO(allada) Remove these and merge it with request interception. | 
|  | this._blockingEnabledSetting = Common.moduleSetting('requestBlockingEnabled'); | 
|  | this._blockedPatternsSetting = Common.settings.createSetting('networkBlockedPatterns', []); | 
|  | this._effectiveBlockedURLs = []; | 
|  | this._updateBlockedPatterns(); | 
|  |  | 
|  | /** @type {!Platform.Multimap<!SDK.MultitargetNetworkManager.RequestInterceptor, !SDK.MultitargetNetworkManager.InterceptionPattern>} */ | 
|  | this._urlsForRequestInterceptor = new Platform.Multimap(); | 
|  |  | 
|  | SDK.targetManager.observeModels(NetworkManager, this); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} uaString | 
|  | * @return {string} | 
|  | */ | 
|  | static patchUserAgentWithChromeVersion(uaString) { | 
|  | // Patches Chrome/CriOS version from user agent ("1.2.3.4" when user agent is: "Chrome/1.2.3.4"). | 
|  | // Edge also contains an appVersion which should be patched to match the Chrome major version. | 
|  | // Otherwise, ignore it. This assumes additional appVersions appear after the Chrome version. | 
|  | const chromeRegex = new RegExp('(?:^|\\W)Chrome/(\\S+)'); | 
|  | const chromeMatch = navigator.userAgent.match(chromeRegex); | 
|  | if (chromeMatch && chromeMatch.length > 1) { | 
|  | // "1.2.3.4" becomes "1.0.100.0" | 
|  | const additionalAppVersion = chromeMatch[1].split('.', 1)[0] + '.0.100.0'; | 
|  | return String.sprintf(uaString, chromeMatch[1], additionalAppVersion); | 
|  | } | 
|  | return uaString; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!NetworkManager} networkManager | 
|  | */ | 
|  | modelAdded(networkManager) { | 
|  | const networkAgent = networkManager.target().networkAgent(); | 
|  | if (this._extraHeaders) { | 
|  | networkAgent.setExtraHTTPHeaders(this._extraHeaders); | 
|  | } | 
|  | if (this._currentUserAgent()) { | 
|  | networkAgent.setUserAgentOverride(this._currentUserAgent()); | 
|  | } | 
|  | if (this._effectiveBlockedURLs.length) { | 
|  | networkAgent.setBlockedURLs(this._effectiveBlockedURLs); | 
|  | } | 
|  | if (this.isIntercepting()) { | 
|  | networkAgent.setRequestInterception(this._urlsForRequestInterceptor.valuesArray()); | 
|  | } | 
|  | this._agents.add(networkAgent); | 
|  | if (this.isThrottling()) { | 
|  | this._updateNetworkConditions(networkAgent); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!NetworkManager} networkManager | 
|  | */ | 
|  | modelRemoved(networkManager) { | 
|  | for (const entry of this._inflightMainResourceRequests) { | 
|  | const manager = NetworkManager.forRequest(/** @type {!SDK.NetworkRequest} */ (entry[1])); | 
|  | if (manager !== networkManager) { | 
|  | continue; | 
|  | } | 
|  | this._inflightMainResourceRequests.delete(/** @type {string} */ (entry[0])); | 
|  | } | 
|  | this._agents.delete(networkManager.target().networkAgent()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | isThrottling() { | 
|  | return this._networkConditions.download >= 0 || this._networkConditions.upload >= 0 || | 
|  | this._networkConditions.latency > 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | isOffline() { | 
|  | return !this._networkConditions.download && !this._networkConditions.upload; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkManager.Conditions} conditions | 
|  | */ | 
|  | setNetworkConditions(conditions) { | 
|  | this._networkConditions = conditions; | 
|  | for (const agent of this._agents) { | 
|  | this._updateNetworkConditions(agent); | 
|  | } | 
|  | this.dispatchEventToListeners(MultitargetNetworkManager.Events.ConditionsChanged); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!SDK.NetworkManager.Conditions} | 
|  | */ | 
|  | networkConditions() { | 
|  | return this._networkConditions; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Protocol.NetworkAgent} networkAgent | 
|  | */ | 
|  | _updateNetworkConditions(networkAgent) { | 
|  | const conditions = this._networkConditions; | 
|  | if (!this.isThrottling()) { | 
|  | networkAgent.emulateNetworkConditions(false, 0, 0, 0); | 
|  | } else { | 
|  | networkAgent.emulateNetworkConditions( | 
|  | this.isOffline(), conditions.latency, conditions.download < 0 ? 0 : conditions.download, | 
|  | conditions.upload < 0 ? 0 : conditions.upload, NetworkManager._connectionType(conditions)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Protocol.Network.Headers} headers | 
|  | */ | 
|  | setExtraHTTPHeaders(headers) { | 
|  | this._extraHeaders = headers; | 
|  | for (const agent of this._agents) { | 
|  | agent.setExtraHTTPHeaders(this._extraHeaders); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {string} | 
|  | */ | 
|  | _currentUserAgent() { | 
|  | return this._customUserAgent ? this._customUserAgent : this._userAgentOverride; | 
|  | } | 
|  |  | 
|  | _updateUserAgentOverride() { | 
|  | const userAgent = this._currentUserAgent(); | 
|  | for (const agent of this._agents) { | 
|  | agent.setUserAgentOverride(userAgent); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} userAgent | 
|  | */ | 
|  | setUserAgentOverride(userAgent) { | 
|  | if (this._userAgentOverride === userAgent) { | 
|  | return; | 
|  | } | 
|  | this._userAgentOverride = userAgent; | 
|  | if (!this._customUserAgent) { | 
|  | this._updateUserAgentOverride(); | 
|  | } | 
|  | this.dispatchEventToListeners(MultitargetNetworkManager.Events.UserAgentChanged); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {string} | 
|  | */ | 
|  | userAgentOverride() { | 
|  | return this._userAgentOverride; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} userAgent | 
|  | */ | 
|  | setCustomUserAgentOverride(userAgent) { | 
|  | this._customUserAgent = userAgent; | 
|  | this._updateUserAgentOverride(); | 
|  | } | 
|  |  | 
|  | // TODO(allada) Move all request blocking into interception and let view manage blocking. | 
|  | /** | 
|  | * @return {!Array<!SDK.NetworkManager.BlockedPattern>} | 
|  | */ | 
|  | blockedPatterns() { | 
|  | return this._blockedPatternsSetting.get().slice(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | blockingEnabled() { | 
|  | return this._blockingEnabledSetting.get(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | isBlocking() { | 
|  | return !!this._effectiveBlockedURLs.length; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Array<!SDK.NetworkManager.BlockedPattern>} patterns | 
|  | */ | 
|  | setBlockedPatterns(patterns) { | 
|  | this._blockedPatternsSetting.set(patterns); | 
|  | this._updateBlockedPatterns(); | 
|  | this.dispatchEventToListeners(MultitargetNetworkManager.Events.BlockedPatternsChanged); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {boolean} enabled | 
|  | */ | 
|  | setBlockingEnabled(enabled) { | 
|  | if (this._blockingEnabledSetting.get() === enabled) { | 
|  | return; | 
|  | } | 
|  | this._blockingEnabledSetting.set(enabled); | 
|  | this._updateBlockedPatterns(); | 
|  | this.dispatchEventToListeners(MultitargetNetworkManager.Events.BlockedPatternsChanged); | 
|  | } | 
|  |  | 
|  | _updateBlockedPatterns() { | 
|  | const urls = []; | 
|  | if (this._blockingEnabledSetting.get()) { | 
|  | for (const pattern of this._blockedPatternsSetting.get()) { | 
|  | if (pattern.enabled) { | 
|  | urls.push(pattern.url); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!urls.length && !this._effectiveBlockedURLs.length) { | 
|  | return; | 
|  | } | 
|  | this._effectiveBlockedURLs = urls; | 
|  | for (const agent of this._agents) { | 
|  | agent.setBlockedURLs(this._effectiveBlockedURLs); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | isIntercepting() { | 
|  | return !!this._urlsForRequestInterceptor.size; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Array<!SDK.MultitargetNetworkManager.InterceptionPattern>} patterns | 
|  | * @param {!SDK.MultitargetNetworkManager.RequestInterceptor} requestInterceptor | 
|  | * @return {!Promise} | 
|  | */ | 
|  | setInterceptionHandlerForPatterns(patterns, requestInterceptor) { | 
|  | // Note: requestInterceptors may recieve interception requests for patterns they did not subscribe to. | 
|  | this._urlsForRequestInterceptor.deleteAll(requestInterceptor); | 
|  | for (const newPattern of patterns) { | 
|  | this._urlsForRequestInterceptor.set(requestInterceptor, newPattern); | 
|  | } | 
|  | return this._updateInterceptionPatternsOnNextTick(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!Promise} | 
|  | */ | 
|  | _updateInterceptionPatternsOnNextTick() { | 
|  | // This is used so we can register and unregister patterns in loops without sending lots of protocol messages. | 
|  | if (!this._updatingInterceptionPatternsPromise) { | 
|  | this._updatingInterceptionPatternsPromise = Promise.resolve().then(this._updateInterceptionPatterns.bind(this)); | 
|  | } | 
|  | return this._updatingInterceptionPatternsPromise; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!Promise} | 
|  | */ | 
|  | _updateInterceptionPatterns() { | 
|  | if (!Common.moduleSetting('cacheDisabled').get()) { | 
|  | Common.moduleSetting('cacheDisabled').set(true); | 
|  | } | 
|  | this._updatingInterceptionPatternsPromise = null; | 
|  | const promises = /** @type {!Array<!Promise>} */ ([]); | 
|  | for (const agent of this._agents) { | 
|  | promises.push(agent.setRequestInterception(this._urlsForRequestInterceptor.valuesArray())); | 
|  | } | 
|  | this.dispatchEventToListeners(MultitargetNetworkManager.Events.InterceptorsChanged); | 
|  | return Promise.all(promises); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!InterceptedRequest} interceptedRequest | 
|  | */ | 
|  | async _requestIntercepted(interceptedRequest) { | 
|  | for (const requestInterceptor of this._urlsForRequestInterceptor.keysArray()) { | 
|  | await requestInterceptor(interceptedRequest); | 
|  | if (interceptedRequest.hasResponded()) { | 
|  | return; | 
|  | } | 
|  | } | 
|  | if (!interceptedRequest.hasResponded()) { | 
|  | interceptedRequest.continueRequestWithoutChange(); | 
|  | } | 
|  | } | 
|  |  | 
|  | clearBrowserCache() { | 
|  | for (const agent of this._agents) { | 
|  | agent.clearBrowserCache(); | 
|  | } | 
|  | } | 
|  |  | 
|  | clearBrowserCookies() { | 
|  | for (const agent of this._agents) { | 
|  | agent.clearBrowserCookies(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} origin | 
|  | * @return {!Promise<!Array<string>>} | 
|  | */ | 
|  | getCertificate(origin) { | 
|  | const target = SDK.targetManager.mainTarget(); | 
|  | return target.networkAgent().getCertificate(origin).then(certificate => certificate || []); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} url | 
|  | * @param {function(number, !Object.<string, string>, string, number)} callback | 
|  | */ | 
|  | loadResource(url, callback) { | 
|  | const headers = {}; | 
|  |  | 
|  | const currentUserAgent = this._currentUserAgent(); | 
|  | if (currentUserAgent) { | 
|  | headers['User-Agent'] = currentUserAgent; | 
|  | } | 
|  |  | 
|  | if (Common.moduleSetting('cacheDisabled').get()) { | 
|  | headers['Cache-Control'] = 'no-cache'; | 
|  | } | 
|  |  | 
|  | Host.ResourceLoader.load(url, headers, callback); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** @enum {symbol} */ | 
|  | MultitargetNetworkManager.Events = { | 
|  | BlockedPatternsChanged: Symbol('BlockedPatternsChanged'), | 
|  | ConditionsChanged: Symbol('ConditionsChanged'), | 
|  | UserAgentChanged: Symbol('UserAgentChanged'), | 
|  | InterceptorsChanged: Symbol('InterceptorsChanged') | 
|  | }; | 
|  |  | 
|  | export class InterceptedRequest { | 
|  | /** | 
|  | * @param {!Protocol.NetworkAgent} networkAgent | 
|  | * @param {!Protocol.Network.InterceptionId} interceptionId | 
|  | * @param {!Protocol.Network.Request} request | 
|  | * @param {!Protocol.Page.FrameId} frameId | 
|  | * @param {!Protocol.Network.ResourceType} resourceType | 
|  | * @param {boolean} isNavigationRequest | 
|  | * @param {boolean=} isDownload | 
|  | * @param {string=} redirectUrl | 
|  | * @param {!Protocol.Network.AuthChallenge=} authChallenge | 
|  | * @param {!Protocol.Network.ErrorReason=} responseErrorReason | 
|  | * @param {number=} responseStatusCode | 
|  | * @param {!Protocol.Network.Headers=} responseHeaders | 
|  | * @param {!Protocol.Network.RequestId=} requestId | 
|  | */ | 
|  | constructor( | 
|  | networkAgent, interceptionId, request, frameId, resourceType, isNavigationRequest, isDownload, redirectUrl, | 
|  | authChallenge, responseErrorReason, responseStatusCode, responseHeaders, requestId) { | 
|  | this._networkAgent = networkAgent; | 
|  | this._interceptionId = interceptionId; | 
|  | this._hasResponded = false; | 
|  | this.request = request; | 
|  | this.frameId = frameId; | 
|  | this.resourceType = resourceType; | 
|  | this.isNavigationRequest = isNavigationRequest; | 
|  | this.isDownload = !!isDownload; | 
|  | this.redirectUrl = redirectUrl; | 
|  | this.authChallenge = authChallenge; | 
|  | this.responseErrorReason = responseErrorReason; | 
|  | this.responseStatusCode = responseStatusCode; | 
|  | this.responseHeaders = responseHeaders; | 
|  | this.requestId = requestId; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | hasResponded() { | 
|  | return this._hasResponded; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Blob} contentBlob | 
|  | */ | 
|  | async continueRequestWithContent(contentBlob) { | 
|  | this._hasResponded = true; | 
|  | const headers = [ | 
|  | 'HTTP/1.1 200 OK', | 
|  | 'Date: ' + (new Date()).toUTCString(), | 
|  | 'Server: Chrome Devtools Request Interceptor', | 
|  | 'Connection: closed', | 
|  | 'Content-Length: ' + contentBlob.size, | 
|  | 'Content-Type: ' + contentBlob.type || 'text/x-unknown', | 
|  | ]; | 
|  | const encodedResponse = await blobToBase64(new Blob([headers.join('\r\n'), '\r\n\r\n', contentBlob])); | 
|  | this._networkAgent.continueInterceptedRequest(this._interceptionId, undefined, encodedResponse); | 
|  |  | 
|  | /** | 
|  | * @param {!Blob} blob | 
|  | * @return {!Promise<string>} | 
|  | */ | 
|  | async function blobToBase64(blob) { | 
|  | const reader = new FileReader(); | 
|  | const fileContentsLoadedPromise = new Promise(resolve => reader.onloadend = resolve); | 
|  | reader.readAsDataURL(blob); | 
|  | await fileContentsLoadedPromise; | 
|  | if (reader.error) { | 
|  | console.error('Could not convert blob to base64.', reader.error); | 
|  | return ''; | 
|  | } | 
|  | const result = reader.result; | 
|  | if (result === undefined) { | 
|  | console.error('Could not convert blob to base64.'); | 
|  | return ''; | 
|  | } | 
|  | return result.substring(result.indexOf(',') + 1); | 
|  | } | 
|  | } | 
|  |  | 
|  | continueRequestWithoutChange() { | 
|  | console.assert(!this._hasResponded); | 
|  | this._hasResponded = true; | 
|  | this._networkAgent.continueInterceptedRequest(this._interceptionId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Protocol.Network.ErrorReason} errorReason | 
|  | */ | 
|  | continueRequestWithError(errorReason) { | 
|  | console.assert(!this._hasResponded); | 
|  | this._hasResponded = true; | 
|  | this._networkAgent.continueInterceptedRequest(this._interceptionId, errorReason); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!Promise<!SDK.NetworkRequest.ContentData>} | 
|  | */ | 
|  | async responseBody() { | 
|  | const response = | 
|  | await this._networkAgent.invoke_getResponseBodyForInterception({interceptionId: this._interceptionId}); | 
|  | const error = response[Protocol.Error] || null; | 
|  | return {error: error, content: error ? null : response.body, encoded: response.base64Encoded}; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper class to match requests created from requestWillBeSent with | 
|  | * requestWillBeSentExtraInfo and responseReceivedExtraInfo when they have the | 
|  | * same requestId due to redirects. | 
|  | */ | 
|  | class RedirectExtraInfoBuilder { | 
|  | /** | 
|  | * @param {function()} deleteCallback | 
|  | */ | 
|  | constructor(deleteCallback) { | 
|  | /** @type {!Array<!SDK.NetworkRequest>} */ | 
|  | this._requests = []; | 
|  | /** @type {!Array<?SDK.NetworkRequest.ExtraRequestInfo>} */ | 
|  | this._requestExtraInfos = []; | 
|  | /** @type {!Array<?SDK.NetworkRequest.ExtraResponseInfo>} */ | 
|  | this._responseExtraInfos = []; | 
|  | /** @type {boolean} */ | 
|  | this._finished = false; | 
|  | /** @type {boolean} */ | 
|  | this._hasExtraInfo = false; | 
|  | /** @type {function()} */ | 
|  | this._deleteCallback = deleteCallback; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest} req | 
|  | */ | 
|  | addRequest(req) { | 
|  | this._requests.push(req); | 
|  | this._sync(this._requests.length - 1); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest.ExtraRequestInfo} info | 
|  | */ | 
|  | addRequestExtraInfo(info) { | 
|  | this._hasExtraInfo = true; | 
|  | this._requestExtraInfos.push(info); | 
|  | this._sync(this._requestExtraInfos.length - 1); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!SDK.NetworkRequest.ExtraResponseInfo} info | 
|  | */ | 
|  | addResponseExtraInfo(info) { | 
|  | this._responseExtraInfos.push(info); | 
|  | this._sync(this._responseExtraInfos.length - 1); | 
|  | } | 
|  |  | 
|  | finished() { | 
|  | this._finished = true; | 
|  | this._deleteIfComplete(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {number} index | 
|  | */ | 
|  | _sync(index) { | 
|  | const req = this._requests[index]; | 
|  | if (!req) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const requestExtraInfo = this._requestExtraInfos[index]; | 
|  | if (requestExtraInfo) { | 
|  | req.addExtraRequestInfo(requestExtraInfo); | 
|  | this._requestExtraInfos[index] = null; | 
|  | } | 
|  |  | 
|  | const responseExtraInfo = this._responseExtraInfos[index]; | 
|  | if (responseExtraInfo) { | 
|  | req.addExtraResponseInfo(responseExtraInfo); | 
|  | this._responseExtraInfos[index] = null; | 
|  | } | 
|  |  | 
|  | this._deleteIfComplete(); | 
|  | } | 
|  |  | 
|  | _deleteIfComplete() { | 
|  | if (!this._finished) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (this._hasExtraInfo) { | 
|  | // if we haven't gotten the last responseExtraInfo event, we have to wait for it. | 
|  | if (!this._requests.peekLast().hasExtraResponseInfo()) { | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | this._deleteCallback(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Legacy exported object */ | 
|  | self.SDK = self.SDK || {}; | 
|  |  | 
|  | /* Legacy exported object */ | 
|  | SDK = SDK || {}; | 
|  |  | 
|  | /** @constructor */ | 
|  | SDK.NetworkManager = NetworkManager; | 
|  |  | 
|  | /** @enum {symbol} */ | 
|  | SDK.NetworkManager.Events = Events; | 
|  |  | 
|  | /** @type {!SDK.NetworkManager.Conditions} */ | 
|  | SDK.NetworkManager.NoThrottlingConditions = NoThrottlingConditions; | 
|  |  | 
|  | /** @type {!SDK.NetworkManager.Conditions} */ | 
|  | SDK.NetworkManager.OfflineConditions = OfflineConditions; | 
|  |  | 
|  | /** @type {!SDK.NetworkManager.Conditions} */ | 
|  | SDK.NetworkManager.Slow3GConditions = Slow3GConditions; | 
|  |  | 
|  | /** @type {!SDK.NetworkManager.Conditions} */ | 
|  | SDK.NetworkManager.Fast3GConditions = Fast3GConditions; | 
|  |  | 
|  | /** @constructor */ | 
|  | SDK.NetworkDispatcher = NetworkDispatcher; | 
|  |  | 
|  | /** @constructor */ | 
|  | SDK.MultitargetNetworkManager = MultitargetNetworkManager; | 
|  |  | 
|  | /** @constructor */ | 
|  | SDK.MultitargetNetworkManager.InterceptedRequest = InterceptedRequest; | 
|  |  | 
|  | /** @typedef {{url: string, enabled: boolean}} */ | 
|  | SDK.NetworkManager.BlockedPattern; | 
|  |  | 
|  | /** | 
|  | * @typedef {{ | 
|  | *   download: number, | 
|  | *   upload: number, | 
|  | *   latency: number, | 
|  | *   title: string, | 
|  | * }} | 
|  | */ | 
|  | SDK.NetworkManager.Conditions; | 
|  |  | 
|  | /** @typedef {{message: string, requestId: string, warning: boolean}} */ | 
|  | SDK.NetworkManager.Message; | 
|  |  | 
|  | /** @typedef {!{urlPattern: string, interceptionStage: !Protocol.Network.InterceptionStage}} */ | 
|  | SDK.MultitargetNetworkManager.InterceptionPattern; | 
|  |  | 
|  | /** @typedef {!function(!InterceptedRequest):!Promise} */ | 
|  | SDK.MultitargetNetworkManager.RequestInterceptor; | 
|  |  | 
|  | /** | 
|  | * @type {!MultitargetNetworkManager} | 
|  | */ | 
|  | SDK.multitargetNetworkManager; | 
|  |  | 
|  | SDK.SDKModel.register(SDK.NetworkManager, SDK.Target.Capability.Network, true); |