mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-10 13:44:39 +08:00
After this CL, the CORB warning should be suppressed for: - Responses with 'Content-Length: 0' response header - Responses with 204 http status. Bug: 844178 Cq-Include-Trybots: master.tryserver.chromium.linux:linux_mojo Change-Id: I320c9e221d94c3deeef26b96a5ff70b41ea1c2a8 Reviewed-on: https://chromium-review.googlesource.com/1070551 Commit-Queue: Łukasz Anforowicz <lukasza@chromium.org> Reviewed-by: Matt Menke <mmenke@chromium.org> Reviewed-by: Nasko Oskov <nasko@chromium.org> Reviewed-by: Pavel Feldman <pfeldman@chromium.org> Reviewed-by: Charlie Reis <creis@chromium.org> Cr-Original-Commit-Position: refs/heads/master@{#564956} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: bf576b0f6991a855a989864b1e79fcb209dd75a1
1330 lines
47 KiB
JavaScript
1330 lines
47 KiB
JavaScript
/*
|
|
* 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
|
|
*/
|
|
SDK.NetworkManager = class extends SDK.SDKModel {
|
|
/**
|
|
* @param {!SDK.Target} target
|
|
*/
|
|
constructor(target) {
|
|
super(target);
|
|
this._dispatcher = new SDK.NetworkDispatcher(this);
|
|
this._networkAgent = target.networkAgent();
|
|
target.registerNetworkDispatcher(this._dispatcher);
|
|
if (Common.moduleSetting('cacheDisabled').get())
|
|
this._networkAgent.setCacheDisabled(true);
|
|
|
|
this._networkAgent.enable(undefined, undefined, SDK.NetworkManager.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 {?SDK.NetworkManager}
|
|
*/
|
|
static forRequest(request) {
|
|
return request[SDK.NetworkManager._networkManagerForRequestSymbol];
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.NetworkRequest} request
|
|
* @return {boolean}
|
|
*/
|
|
static canReplayRequest(request) {
|
|
return !!request[SDK.NetworkManager._networkManagerForRequestSymbol] &&
|
|
request.resourceType() === Common.resourceTypes.XHR;
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.NetworkRequest} request
|
|
*/
|
|
static replayRequest(request) {
|
|
const manager = request[SDK.NetworkManager._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 = SDK.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 = SDK.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 = SDK.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 = SDK.NetworkManager._connectionTypes;
|
|
if (!types) {
|
|
SDK.NetworkManager._connectionTypes = [];
|
|
types = SDK.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());
|
|
}
|
|
};
|
|
|
|
SDK.SDKModel.register(SDK.NetworkManager, SDK.Target.Capability.Network, true);
|
|
|
|
/** @enum {symbol} */
|
|
SDK.NetworkManager.Events = {
|
|
RequestStarted: Symbol('RequestStarted'),
|
|
RequestUpdated: Symbol('RequestUpdated'),
|
|
RequestFinished: Symbol('RequestFinished'),
|
|
RequestUpdateDropped: Symbol('RequestUpdateDropped'),
|
|
ResponseReceived: Symbol('ResponseReceived'),
|
|
MessageGenerated: Symbol('MessageGenerated'),
|
|
RequestRedirected: Symbol('RequestRedirected'),
|
|
};
|
|
|
|
/** @typedef {{message: string, requestId: string, warning: boolean}} */
|
|
SDK.NetworkManager.Message;
|
|
|
|
SDK.NetworkManager._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},
|
|
};
|
|
|
|
/**
|
|
* @typedef {{
|
|
* download: number,
|
|
* upload: number,
|
|
* latency: number,
|
|
* title: string,
|
|
* }}
|
|
**/
|
|
SDK.NetworkManager.Conditions;
|
|
|
|
/** @type {!SDK.NetworkManager.Conditions} */
|
|
SDK.NetworkManager.NoThrottlingConditions = {
|
|
title: Common.UIString('Online'),
|
|
download: -1,
|
|
upload: -1,
|
|
latency: 0
|
|
};
|
|
|
|
/** @type {!SDK.NetworkManager.Conditions} */
|
|
SDK.NetworkManager.OfflineConditions = {
|
|
title: Common.UIString('Offline'),
|
|
download: 0,
|
|
upload: 0,
|
|
latency: 0,
|
|
};
|
|
|
|
/** @type {!SDK.NetworkManager.Conditions} */
|
|
SDK.NetworkManager.Slow3GConditions = {
|
|
title: Common.UIString('Slow 3G'),
|
|
download: 500 * 1024 / 8 * .8,
|
|
upload: 500 * 1024 / 8 * .8,
|
|
latency: 400 * 5,
|
|
};
|
|
|
|
/** @type {!SDK.NetworkManager.Conditions} */
|
|
SDK.NetworkManager.Fast3GConditions = {
|
|
title: Common.UIString('Fast 3G'),
|
|
download: 1.6 * 1024 * 1024 / 8 * .9,
|
|
upload: 750 * 1024 / 8 * .9,
|
|
latency: 150 * 3.75,
|
|
};
|
|
|
|
/** @typedef {{url: string, enabled: boolean}} */
|
|
SDK.NetworkManager.BlockedPattern;
|
|
|
|
SDK.NetworkManager._networkManagerForRequestSymbol = Symbol('NetworkManager');
|
|
|
|
SDK.NetworkManager.MAX_EAGER_POST_REQUEST_BODY_LENGTH = 64 * 1024; // bytes
|
|
|
|
/**
|
|
* @implements {Protocol.NetworkDispatcher}
|
|
* @unrestricted
|
|
*/
|
|
SDK.NetworkDispatcher = class {
|
|
/**
|
|
* @param {!SDK.NetworkManager} manager
|
|
*/
|
|
constructor(manager) {
|
|
this._manager = manager;
|
|
/** @type {!Object<!Protocol.Network.RequestId, !SDK.NetworkRequest>} */
|
|
this._inflightRequestsById = {};
|
|
/** @type {!Object<string, !SDK.NetworkRequest>} */
|
|
this._inflightRequestsByURL = {};
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
networkRequest.responseHeaders = this._headersMapToHeadersArray(response.headers);
|
|
if (response.encodedDataLength >= 0)
|
|
networkRequest.setTransferSize(response.encodedDataLength);
|
|
if (response.headersText)
|
|
networkRequest.responseHeadersText = response.headersText;
|
|
if (response.requestHeaders) {
|
|
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();
|
|
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(
|
|
SDK.NetworkManager.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 SDK.NetworkManager._MIMETypes)
|
|
return resourceType.name() in SDK.NetworkManager._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(SDK.NetworkManager.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.Page.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.Page.ResourceType.Other, redirectResponse, frameId);
|
|
networkRequest = this._appendRedirect(requestId, time, request.url);
|
|
this._manager.dispatchEventToListeners(SDK.NetworkManager.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.Page.ResourceType.Other);
|
|
|
|
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.Page.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 = SDK.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(SDK.NetworkManager.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 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(
|
|
SDK.NetworkManager.Events.MessageGenerated, {message: message, requestId: requestId, warning: true});
|
|
}
|
|
|
|
if ('public-key-pins' in lowercaseHeaders || 'public-key-pins-report-only' in lowercaseHeaders) {
|
|
if (!this._hpkpDomains)
|
|
this._hpkpDomains = new Set();
|
|
const parsed = new Common.ParsedURL(response.url);
|
|
if (parsed.isValid && !this._hpkpDomains.has(parsed.host)) {
|
|
this._hpkpDomains.add(parsed.host);
|
|
const message = Common.UIString(
|
|
'HTTP-Based Public Key Pinning is deprecated. Chrome 69 and later will ignore HPKP response headers. (Host: %s)',
|
|
parsed.host);
|
|
this._manager.dispatchEventToListeners(
|
|
SDK.NetworkManager.Events.MessageGenerated, {message: message, requestId: requestId, warning: true});
|
|
}
|
|
}
|
|
|
|
this._updateNetworkRequestWithResponse(networkRequest, response);
|
|
|
|
this._updateNetworkRequest(networkRequest);
|
|
this._manager.dispatchEventToListeners(SDK.NetworkManager.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._finishNetworkRequest(networkRequest, finishTime, encodedDataLength, shouldReportCorbBlocking);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!Protocol.Network.RequestId} requestId
|
|
* @param {!Protocol.Network.MonotonicTime} time
|
|
* @param {!Protocol.Page.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(
|
|
SDK.NetworkManager.Events.MessageGenerated, {message: message, requestId: requestId, warning: true});
|
|
}
|
|
}
|
|
networkRequest.localizedFailDescription = localizedDescription;
|
|
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[SDK.NetworkManager._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.addFrame(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.addFrame(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.addFrameError(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.Page.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
|
|
*/
|
|
requestIntercepted(
|
|
interceptionId, request, frameId, resourceType, isNavigationRequest, isDownload, redirectUrl, authChallenge,
|
|
responseErrorReason, responseStatusCode, responseHeaders) {
|
|
SDK.multitargetNetworkManager._requestIntercepted(new SDK.MultitargetNetworkManager.InterceptedRequest(
|
|
this._manager.target().networkAgent(), interceptionId, request, frameId, resourceType, isNavigationRequest,
|
|
isDownload, redirectUrl, authChallenge, responseErrorReason, responseStatusCode, responseHeaders));
|
|
}
|
|
|
|
/**
|
|
* @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 = SDK.NetworkManager.forRequest(request)._dispatcher;
|
|
delete oldDispatcher._inflightRequestsById[requestId];
|
|
delete oldDispatcher._inflightRequestsByURL[request.url()];
|
|
this._inflightRequestsById[requestId] = request;
|
|
this._inflightRequestsByURL[request.url()] = request;
|
|
request[SDK.NetworkManager._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);
|
|
delete this._hpkpDomains;
|
|
}
|
|
|
|
this._manager.dispatchEventToListeners(SDK.NetworkManager.Events.RequestStarted, networkRequest);
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.NetworkRequest} networkRequest
|
|
*/
|
|
_updateNetworkRequest(networkRequest) {
|
|
this._manager.dispatchEventToListeners(SDK.NetworkManager.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)
|
|
networkRequest.setTransferSize(encodedDataLength);
|
|
this._manager.dispatchEventToListeners(SDK.NetworkManager.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(
|
|
SDK.NetworkManager.Events.MessageGenerated,
|
|
{message: message, requestId: networkRequest.requestId(), warning: true});
|
|
}
|
|
|
|
if (Common.moduleSetting('monitoringXHREnabled').get() &&
|
|
networkRequest.resourceType().category() === Common.resourceCategories.XHR) {
|
|
const message = Common.UIString(
|
|
(networkRequest.failed || networkRequest.hasErrorStatusCode()) ? '%s failed loading: %s "%s".' :
|
|
'%s finished loading: %s "%s".',
|
|
networkRequest.resourceType().title(), networkRequest.requestMethod, networkRequest.url());
|
|
this._manager.dispatchEventToListeners(
|
|
SDK.NetworkManager.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[SDK.NetworkManager._networkManagerForRequestSymbol] = this._manager;
|
|
return request;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @implements {SDK.TargetManager.Observer}
|
|
* @unrestricted
|
|
*/
|
|
SDK.MultitargetNetworkManager = class 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 = SDK.NetworkManager.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 {!Multimap<!SDK.MultitargetNetworkManager.RequestInterceptor, !SDK.MultitargetNetworkManager.InterceptionPattern>} */
|
|
this._urlsForRequestInterceptor = new Multimap();
|
|
|
|
SDK.targetManager.observeTargets(this, SDK.Target.Capability.Network);
|
|
}
|
|
|
|
/**
|
|
* @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").
|
|
const chromeRegex = new RegExp('(?:^|\\W)Chrome/(\\S+)');
|
|
const chromeMatch = navigator.userAgent.match(chromeRegex);
|
|
if (chromeMatch && chromeMatch.length > 1)
|
|
return String.sprintf(uaString, chromeMatch[1]);
|
|
return uaString;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!SDK.Target} target
|
|
*/
|
|
targetAdded(target) {
|
|
const networkAgent = 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 {!SDK.Target} target
|
|
*/
|
|
targetRemoved(target) {
|
|
for (const entry of this._inflightMainResourceRequests) {
|
|
const manager = SDK.NetworkManager.forRequest(/** @type {!SDK.NetworkRequest} */ (entry[1]));
|
|
if (manager.target() !== target)
|
|
continue;
|
|
this._inflightMainResourceRequests.delete(/** @type {string} */ (entry[0]));
|
|
}
|
|
this._agents.delete(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(SDK.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, SDK.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(SDK.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(SDK.MultitargetNetworkManager.Events.BlockedPatternsChanged);
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} enabled
|
|
*/
|
|
setBlockingEnabled(enabled) {
|
|
if (this._blockingEnabledSetting.get() === enabled)
|
|
return;
|
|
this._blockingEnabledSetting.set(enabled);
|
|
this._updateBlockedPatterns();
|
|
this.dispatchEventToListeners(SDK.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(SDK.MultitargetNetworkManager.Events.InterceptorsChanged);
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.MultitargetNetworkManager.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)} 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} */
|
|
SDK.MultitargetNetworkManager.Events = {
|
|
BlockedPatternsChanged: Symbol('BlockedPatternsChanged'),
|
|
ConditionsChanged: Symbol('ConditionsChanged'),
|
|
UserAgentChanged: Symbol('UserAgentChanged'),
|
|
InterceptorsChanged: Symbol('InterceptorsChanged')
|
|
};
|
|
|
|
SDK.MultitargetNetworkManager.InterceptedRequest = class {
|
|
/**
|
|
* @param {!Protocol.NetworkAgent} networkAgent
|
|
* @param {!Protocol.Network.InterceptionId} interceptionId
|
|
* @param {!Protocol.Network.Request} request
|
|
* @param {!Protocol.Page.FrameId} frameId
|
|
* @param {!Protocol.Page.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
|
|
*/
|
|
constructor(
|
|
networkAgent, interceptionId, request, frameId, resourceType, isNavigationRequest, isDownload, redirectUrl,
|
|
authChallenge, responseErrorReason, responseStatusCode, responseHeaders) {
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @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};
|
|
}
|
|
};
|
|
|
|
/** @typedef {!{urlPattern: string, interceptionStage: !Protocol.Network.InterceptionStage}} */
|
|
SDK.MultitargetNetworkManager.InterceptionPattern;
|
|
|
|
/** @typedef {!function(!SDK.MultitargetNetworkManager.InterceptedRequest):!Promise} */
|
|
SDK.MultitargetNetworkManager.RequestInterceptor;
|
|
|
|
/**
|
|
* @type {!SDK.MultitargetNetworkManager}
|
|
*/
|
|
SDK.multitargetNetworkManager;
|