chrome-devtools-frontend
Version:
Chrome DevTools UI
1,406 lines (1,227 loc) • 79.7 kB
text/typescript
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Protocol from '../../generated/protocol.js';
import * as TextUtils from '../../models/text_utils/text_utils.js';
import * as Common from '../common/common.js';
import * as i18n from '../i18n/i18n.js';
import * as Platform from '../platform/platform.js';
import {Attribute, type Cookie} from './Cookie.js';
import {CookieModel} from './CookieModel.js';
import {CookieParser} from './CookieParser.js';
import * as HttpReasonPhraseStrings from './HttpReasonPhraseStrings.js';
import {
Events as NetworkManagerEvents,
NetworkManager,
} from './NetworkManager.js';
import {ServerSentEvents} from './ServerSentEvents.js';
import {ServerTiming} from './ServerTiming.js';
import {Type} from './Target.js';
// clang-format off
const UIStrings = {
/**
* @description Text in Network Request
*/
binary: '(binary)',
/**
* @description Tooltip to explain why a cookie was blocked
*/
secureOnly: 'This cookie was blocked because it had the "`Secure`" attribute and the connection was not secure.',
/**
* @description Tooltip to explain why a cookie was blocked
*/
notOnPath: 'This cookie was blocked because its path was not an exact match for or a superdirectory of the request url\'s path.',
/**
* @description Tooltip to explain why a cookie was blocked
*/
domainMismatch: 'This cookie was blocked because neither did the request URL\'s domain exactly match the cookie\'s domain, nor was the request URL\'s domain a subdomain of the cookie\'s Domain attribute value.',
/**
* @description Tooltip to explain why a cookie was blocked
*/
sameSiteStrict: 'This cookie was blocked because it had the "`SameSite=Strict`" attribute and the request was made from a different site. This includes top-level navigation requests initiated by other sites.',
/**
* @description Tooltip to explain why a cookie was blocked
*/
sameSiteLax: 'This cookie was blocked because it had the "`SameSite=Lax`" attribute and the request was made from a different site and was not initiated by a top-level navigation.',
/**
* @description Tooltip to explain why a cookie was blocked
*/
sameSiteUnspecifiedTreatedAsLax: 'This cookie didn\'t specify a "`SameSite`" attribute when it was stored and was defaulted to "SameSite=Lax," and was blocked because the request was made from a different site and was not initiated by a top-level navigation. The cookie had to have been set with "`SameSite=None`" to enable cross-site usage.',
/**
* @description Tooltip to explain why a cookie was blocked
*/
sameSiteNoneInsecure: 'This cookie was blocked because it had the "`SameSite=None`" attribute but was not marked "Secure". Cookies without SameSite restrictions must be marked "Secure" and sent over a secure connection.',
/**
* @description Tooltip to explain why a cookie was blocked
*/
userPreferences: 'This cookie was blocked due to user preferences.',
/**
* @description Tooltip to explain why a cookie was blocked
*/
thirdPartyPhaseout: 'This cookie was blocked either because of Chrome flags or browser configuration. Learn more in the Issues panel.',
/**
* @description Tooltip to explain why a cookie was blocked
*/
unknownError: 'An unknown error was encountered when trying to send this cookie.',
/**
* @description Tooltip to explain why a cookie was blocked due to Schemeful Same-Site
*/
schemefulSameSiteStrict: 'This cookie was blocked because it had the "`SameSite=Strict`" attribute but the request was cross-site. This includes top-level navigation requests initiated by other sites. This request is considered cross-site because the URL has a different scheme than the current site.',
/**
* @description Tooltip to explain why a cookie was blocked due to Schemeful Same-Site
*/
schemefulSameSiteLax: 'This cookie was blocked because it had the "`SameSite=Lax`" attribute but the request was cross-site and was not initiated by a top-level navigation. This request is considered cross-site because the URL has a different scheme than the current site.',
/**
* @description Tooltip to explain why a cookie was blocked due to Schemeful Same-Site
*/
schemefulSameSiteUnspecifiedTreatedAsLax: 'This cookie didn\'t specify a "`SameSite`" attribute when it was stored, was defaulted to "`SameSite=Lax"`, and was blocked because the request was cross-site and was not initiated by a top-level navigation. This request is considered cross-site because the URL has a different scheme than the current site.',
/**
* @description Tooltip to explain why a cookie was blocked due to SameParty
*/
samePartyFromCrossPartyContext: 'This cookie was blocked because it had the "`SameParty`" attribute but the request was cross-party. The request was considered cross-party because the domain of the resource\'s URL and the domains of the resource\'s enclosing frames/documents are neither owners nor members in the same First-Party Set.',
/**
* @description Tooltip to explain why a cookie was blocked due to exceeding the maximum size
*/
nameValuePairExceedsMaxSize: 'This cookie was blocked because it was too large. The combined size of the name and value must be less than or equal to 4096 characters.',
/**
* @description Tooltip to explain why an attempt to set a cookie via `Set-Cookie` HTTP header on a request's response was blocked.
*/
thisSetcookieWasBlockedDueToUser: 'This attempt to set a cookie via a `Set-Cookie` header was blocked due to user preferences.',
/**
* @description Tooltip to explain why an attempt to set a cookie via `Set-Cookie` HTTP header on a request's response was blocked.
*/
thisSetcookieWasBlockedDueThirdPartyPhaseout: 'Setting this cookie was blocked either because of Chrome flags or browser configuration. Learn more in the Issues panel.',
/**
* @description Tooltip to explain why an attempt to set a cookie via `Set-Cookie` HTTP header on a request's response was blocked.
*/
thisSetcookieHadInvalidSyntax: 'This `Set-Cookie` header had invalid syntax.',
/**
* @description Tooltip to explain why a cookie was blocked
*/
thisSetcookieHadADisallowedCharacter: 'This `Set-Cookie` header contained a disallowed character (a forbidden ASCII control character, or the tab character if it appears in the middle of the cookie name, value, an attribute name, or an attribute value).',
/**
* @description Tooltip to explain why a cookie was blocked
*/
theSchemeOfThisConnectionIsNot: 'The scheme of this connection is not allowed to store cookies.',
/**
* @description Tooltip to explain why a cookie was blocked
*/
anUnknownErrorWasEncounteredWhenTrying: 'An unknown error was encountered when trying to store this cookie.',
/**
* @description Tooltip to explain why a cookie was blocked due to Schemeful Same-Site
* @example {SameSite=Strict} PH1
*/
thisSetcookieWasBlockedBecauseItHadTheSamesiteStrictLax: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because it had the "{PH1}" attribute but came from a cross-site response which was not the response to a top-level navigation. This response is considered cross-site because the URL has a different scheme than the current site.',
/**
* @description Tooltip to explain why a cookie was blocked due to Schemeful Same-Site
*/
thisSetcookieDidntSpecifyASamesite: 'This `Set-Cookie` header didn\'t specify a "`SameSite`" attribute, was defaulted to "`SameSite=Lax"`, and was blocked because it came from a cross-site response which was not the response to a top-level navigation. This response is considered cross-site because the URL has a different scheme than the current site.',
/**
* @description Tooltip to explain why a cookie was blocked due to SameParty
*/
thisSetcookieWasBlockedBecauseItHadTheSameparty: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because it had the "`SameParty`" attribute but the request was cross-party. The request was considered cross-party because the domain of the resource\'s URL and the domains of the resource\'s enclosing frames/documents are neither owners nor members in the same First-Party Set.',
/**
* @description Tooltip to explain why a cookie was blocked due to SameParty
*/
thisSetcookieWasBlockedBecauseItHadTheSamepartyAttribute: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because it had the "`SameParty`" attribute but also had other conflicting attributes. Chrome requires cookies that use the "`SameParty`" attribute to also have the "Secure" attribute, and to not be restricted to "`SameSite=Strict`".',
/**
* @description Tooltip to explain why an attempt to set a cookie via a `Set-Cookie` HTTP header on a request's response was blocked.
*/
blockedReasonSecureOnly: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because it had the "Secure" attribute but was not received over a secure connection.',
/**
* @description Tooltip to explain why an attempt to set a cookie via a `Set-Cookie` HTTP header on a request's response was blocked.
* @example {SameSite=Strict} PH1
*/
blockedReasonSameSiteStrictLax: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because it had the "{PH1}" attribute but came from a cross-site response which was not the response to a top-level navigation.',
/**
* @description Tooltip to explain why an attempt to set a cookie via a `Set-Cookie` HTTP header on a request's response was blocked.
*/
blockedReasonSameSiteUnspecifiedTreatedAsLax: 'This `Set-Cookie` header didn\'t specify a "`SameSite`" attribute and was defaulted to "`SameSite=Lax,`" and was blocked because it came from a cross-site response which was not the response to a top-level navigation. The `Set-Cookie` had to have been set with "`SameSite=None`" to enable cross-site usage.',
/**
* @description Tooltip to explain why an attempt to set a cookie via a `Set-Cookie` HTTP header on a request's response was blocked.
*/
blockedReasonSameSiteNoneInsecure: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because it had the "`SameSite=None`" attribute but did not have the "Secure" attribute, which is required in order to use "`SameSite=None`".',
/**
* @description Tooltip to explain why an attempt to set a cookie via a `Set-Cookie` HTTP header on a request's response was blocked.
*/
blockedReasonOverwriteSecure: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because it was not sent over a secure connection and would have overwritten a cookie with the Secure attribute.',
/**
* @description Tooltip to explain why an attempt to set a cookie via a `Set-Cookie` HTTP header on a request's response was blocked.
*/
blockedReasonInvalidDomain: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because its Domain attribute was invalid with regards to the current host url.',
/**
* @description Tooltip to explain why an attempt to set a cookie via a `Set-Cookie` HTTP header on a request's response was blocked.
*/
blockedReasonInvalidPrefix: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because it used the "`__Secure-`" or "`__Host-`" prefix in its name and broke the additional rules applied to cookies with these prefixes as defined in `https://tools.ietf.org/html/draft-west-cookie-prefixes-05`.',
/**
* @description Tooltip to explain why a cookie was blocked when the size of the #name plus the size of the value exceeds the max size.
*/
thisSetcookieWasBlockedBecauseTheNameValuePairExceedsMaxSize: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because the cookie was too large. The combined size of the name and value must be less than or equal to 4096 characters.',
/**
* @description Text in Network Manager
* @example {https://example.com} PH1
*/
setcookieHeaderIsIgnoredIn: 'Set-Cookie header is ignored in response from url: {PH1}. The combined size of the name and value must be less than or equal to 4096 characters.',
/**
* @description Tooltip to explain why the cookie should have been blocked by third-party cookie phaseout but is exempted.
*/
exemptionReasonUserSetting: 'This cookie is allowed by user preference.',
/**
* @description Tooltip to explain why the cookie should have been blocked by third-party cookie phaseout but is exempted.
*/
exemptionReasonTPCDMetadata: 'This cookie is allowed by a third-party cookie deprecation trial grace period. Learn more: goo.gle/dt-grace.',
/**
* @description Tooltip to explain why the cookie should have been blocked by third-party cookie phaseout but is exempted.
*/
exemptionReasonTPCDDeprecationTrial: 'This cookie is allowed by third-party cookie deprecation trial. Learn more: goo.gle/ps-dt.',
/**
* @description Tooltip to explain why the cookie should have been blocked by third-party cookie phaseout but is exempted.
*/
exemptionReasonTopLevelTPCDDeprecationTrial: 'This cookie is allowed by top-level third-party cookie deprecation trial. Learn more: goo.gle/ps-dt.',
/**
* @description Tooltip to explain why the cookie should have been blocked by third-party cookie phaseout but is exempted.
*/
exemptionReasonTPCDHeuristics: 'This cookie is allowed by third-party cookie heuristics. Learn more: goo.gle/hbe',
/**
* @description Tooltip to explain why the cookie should have been blocked by third-party cookie phaseout but is exempted.
*/
exemptionReasonEnterprisePolicy: 'This cookie is allowed by Chrome Enterprise policy. Learn more: goo.gle/ce-3pc',
/**
* @description Tooltip to explain why the cookie should have been blocked by third-party cookie phaseout but is exempted.
*/
exemptionReasonStorageAccessAPI: 'This cookie is allowed by the Storage Access API. Learn more: goo.gle/saa',
/**
* @description Tooltip to explain why the cookie should have been blocked by third-party cookie phaseout but is exempted.
*/
exemptionReasonTopLevelStorageAccessAPI: 'This cookie is allowed by the top-level Storage Access API. Learn more: goo.gle/saa-top',
/**
* @description Tooltip to explain why the cookie should have been blocked by third-party cookie phaseout but is exempted.
*/
exemptionReasonScheme: 'This cookie is allowed by the top-level url scheme',
} as const;
// clang-format on
const str_ = i18n.i18n.registerUIStrings('core/sdk/NetworkRequest.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventTypes> implements
TextUtils.ContentProvider.StreamingContentProvider {
#requestId: string;
#backendRequestId?: Protocol.Network.RequestId;
readonly #documentURL: Platform.DevToolsPath.UrlString;
readonly #frameId: Protocol.Page.FrameId|null;
readonly #loaderId: Protocol.Network.LoaderId|null;
readonly #hasUserGesture: boolean|undefined;
readonly #initiator: Protocol.Network.Initiator|null|undefined;
#redirectSource: NetworkRequest|null = null;
#preflightRequest: NetworkRequest|null = null;
#preflightInitiatorRequest: NetworkRequest|null = null;
#isRedirect = false;
#redirectDestination: NetworkRequest|null = null;
#issueTime = -1;
#startTime = -1;
#endTime = -1;
#blockedReason: Protocol.Network.BlockedReason|undefined = undefined;
#corsErrorStatus: Protocol.Network.CorsErrorStatus|undefined = undefined;
statusCode = 0;
statusText = '';
requestMethod = '';
requestTime = 0;
protocol = '';
alternateProtocolUsage: Protocol.Network.AlternateProtocolUsage|undefined = undefined;
mixedContentType: Protocol.Security.MixedContentType = Protocol.Security.MixedContentType.None;
#initialPriority: Protocol.Network.ResourcePriority|null = null;
#currentPriority: Protocol.Network.ResourcePriority|null = null;
#signedExchangeInfo: Protocol.Network.SignedExchangeInfo|null = null;
#webBundleInfo: WebBundleInfo|null = null;
#webBundleInnerRequestInfo: WebBundleInnerRequestInfo|null = null;
#resourceType: Common.ResourceType.ResourceType = Common.ResourceType.resourceTypes.Other;
#contentData: Promise<TextUtils.ContentData.ContentDataOrError>|null = null;
#streamingContentData: Promise<TextUtils.StreamingContentData.StreamingContentDataOrError>|null = null;
readonly #frames: WebSocketFrame[] = [];
#responseHeaderValues: Record<string, string|undefined> = {};
#responseHeadersText = '';
#originalResponseHeaders: Protocol.Fetch.HeaderEntry[] = [];
#sortedOriginalResponseHeaders?: NameValue[];
// This field is only used when intercepting and overriding requests, because
// in that case 'this.responseHeaders' does not contain 'set-cookie' headers.
#setCookieHeaders: Protocol.Fetch.HeaderEntry[] = [];
#requestHeaders: NameValue[] = [];
#requestHeaderValues: Record<string, string|undefined> = {};
#remoteAddress = '';
#remoteAddressSpace: Protocol.Network.IPAddressSpace = Protocol.Network.IPAddressSpace.Unknown;
#referrerPolicy: Protocol.Network.RequestReferrerPolicy|null = null;
#securityState: Protocol.Security.SecurityState = Protocol.Security.SecurityState.Unknown;
#securityDetails: Protocol.Network.SecurityDetails|null = null;
connectionId = '0';
connectionReused = false;
hasNetworkData = false;
#formParametersPromise: Promise<NameValue[]|null>|null = null;
#requestFormDataPromise: Promise<string|null>|null = Promise.resolve(null);
#hasExtraRequestInfo = false;
#hasExtraResponseInfo = false;
#blockedRequestCookies: BlockedCookieWithReason[] = [];
#includedRequestCookies: IncludedCookieWithReason[] = [];
#blockedResponseCookies: BlockedSetCookieWithReason[] = [];
#exemptedResponseCookies: ExemptedSetCookieWithReason[] = [];
#responseCookiesPartitionKey: Protocol.Network.CookiePartitionKey|null = null;
#responseCookiesPartitionKeyOpaque: boolean|null = null;
#siteHasCookieInOtherPartition = false;
localizedFailDescription: string|null = null;
#url!: Platform.DevToolsPath.UrlString;
#responseReceivedTime!: number;
#transferSize!: number;
#finished!: boolean;
#failed!: boolean;
#canceled!: boolean;
#preserved!: boolean;
#mimeType!: string;
#charset!: string;
#parsedURL!: Common.ParsedURL.ParsedURL;
#name!: string|undefined;
#path!: string|undefined;
#clientSecurityState!:|Protocol.Network.ClientSecurityState|undefined;
#trustTokenParams!: Protocol.Network.TrustTokenParams|undefined;
#trustTokenOperationDoneEvent!:|Protocol.Network.TrustTokenOperationDoneEvent|undefined;
#responseCacheStorageCacheName?: string;
#serviceWorkerResponseSource?: Protocol.Network.ServiceWorkerResponseSource;
#wallIssueTime?: number;
#responseRetrievalTime?: Date;
#resourceSize?: number;
#fromMemoryCache?: boolean;
#fromDiskCache?: boolean;
#fromPrefetchCache?: boolean;
#fromEarlyHints?: boolean;
#fetchedViaServiceWorker?: boolean;
#serviceWorkerRouterInfo?: Protocol.Network.ServiceWorkerRouterInfo;
#timing?: Protocol.Network.ResourceTiming;
#requestHeadersText?: string;
#responseHeaders?: NameValue[];
#earlyHintsHeaders?: NameValue[];
#sortedResponseHeaders?: NameValue[];
#responseCookies?: Cookie[];
#serverTimings?: ServerTiming[]|null;
#queryString?: string|null;
#parsedQueryParameters?: NameValue[];
#contentDataProvider?: () => Promise<TextUtils.ContentData.ContentDataOrError>;
#isSameSite: boolean|null = null;
#wasIntercepted = false;
#associatedData = new Map<string, object>();
#hasOverriddenContent = false;
#hasThirdPartyCookiePhaseoutIssue = false;
#serverSentEvents?: ServerSentEvents;
responseReceivedPromise?: Promise<void>;
responseReceivedPromiseResolve?: () => void;
directSocketInfo?: DirectSocketInfo;
readonly #directSocketChunks: DirectSocketChunk[] = [];
#isIpProtectionUsed: boolean;
constructor(
requestId: string,
backendRequestId: Protocol.Network.RequestId|undefined,
url: Platform.DevToolsPath.UrlString,
documentURL: Platform.DevToolsPath.UrlString,
frameId: Protocol.Page.FrameId|null,
loaderId: Protocol.Network.LoaderId|null,
initiator: Protocol.Network.Initiator|null,
hasUserGesture?: boolean,
) {
super();
this.#requestId = requestId;
this.#backendRequestId = backendRequestId;
this.setUrl(url);
this.#documentURL = documentURL;
this.#frameId = frameId;
this.#loaderId = loaderId;
this.#initiator = initiator;
this.#hasUserGesture = hasUserGesture;
this.#isIpProtectionUsed = false;
}
static create(
backendRequestId: Protocol.Network.RequestId,
url: Platform.DevToolsPath.UrlString,
documentURL: Platform.DevToolsPath.UrlString,
frameId: Protocol.Page.FrameId|null,
loaderId: Protocol.Network.LoaderId|null,
initiator: Protocol.Network.Initiator|null,
hasUserGesture?: boolean,
): NetworkRequest {
return new NetworkRequest(
backendRequestId,
backendRequestId,
url,
documentURL,
frameId,
loaderId,
initiator,
hasUserGesture,
);
}
static createForSocket(
backendRequestId: Protocol.Network.RequestId,
requestURL: Platform.DevToolsPath.UrlString,
initiator?: Protocol.Network.Initiator,
): NetworkRequest {
return new NetworkRequest(
backendRequestId,
backendRequestId,
requestURL,
Platform.DevToolsPath.EmptyUrlString,
null,
null,
initiator || null,
);
}
static createWithoutBackendRequest(
requestId: string,
url: Platform.DevToolsPath.UrlString,
documentURL: Platform.DevToolsPath.UrlString,
initiator: Protocol.Network.Initiator|null,
): NetworkRequest {
return new NetworkRequest(
requestId,
undefined,
url,
documentURL,
null,
null,
initiator,
);
}
identityCompare(other: NetworkRequest): number {
const thisId = this.requestId();
const thatId = other.requestId();
if (thisId > thatId) {
return 1;
}
if (thisId < thatId) {
return -1;
}
return 0;
}
requestId(): string {
return this.#requestId;
}
backendRequestId(): Protocol.Network.RequestId|undefined {
return this.#backendRequestId;
}
url(): Platform.DevToolsPath.UrlString {
return this.#url;
}
isBlobRequest(): boolean {
return Common.ParsedURL.schemeIs(this.#url, 'blob:');
}
setUrl(x: Platform.DevToolsPath.UrlString): void {
if (this.#url === x) {
return;
}
this.#url = x;
this.#parsedURL = new Common.ParsedURL.ParsedURL(x);
this.#queryString = undefined;
this.#parsedQueryParameters = undefined;
this.#name = undefined;
this.#path = undefined;
}
get documentURL(): Platform.DevToolsPath.UrlString {
return this.#documentURL;
}
get parsedURL(): Common.ParsedURL.ParsedURL {
return this.#parsedURL;
}
get frameId(): Protocol.Page.FrameId|null {
return this.#frameId;
}
get loaderId(): Protocol.Network.LoaderId|null {
return this.#loaderId;
}
setRemoteAddress(ip: string, port: number): void {
this.#remoteAddress = ip + ':' + port;
this.dispatchEventToListeners(Events.REMOTE_ADDRESS_CHANGED, this);
}
remoteAddress(): string {
return this.#remoteAddress;
}
remoteAddressSpace(): Protocol.Network.IPAddressSpace {
return this.#remoteAddressSpace;
}
/**
* The cache #name of the CacheStorage from where the response is served via
* the ServiceWorker.
*/
getResponseCacheStorageCacheName(): string|undefined {
return this.#responseCacheStorageCacheName;
}
setResponseCacheStorageCacheName(x: string): void {
this.#responseCacheStorageCacheName = x;
}
serviceWorkerResponseSource():|Protocol.Network.ServiceWorkerResponseSource|undefined {
return this.#serviceWorkerResponseSource;
}
setServiceWorkerResponseSource(
serviceWorkerResponseSource: Protocol.Network.ServiceWorkerResponseSource,
): void {
this.#serviceWorkerResponseSource = serviceWorkerResponseSource;
}
setReferrerPolicy(
referrerPolicy: Protocol.Network.RequestReferrerPolicy,
): void {
this.#referrerPolicy = referrerPolicy;
}
referrerPolicy(): Protocol.Network.RequestReferrerPolicy|null {
return this.#referrerPolicy;
}
securityState(): Protocol.Security.SecurityState {
return this.#securityState;
}
setSecurityState(securityState: Protocol.Security.SecurityState): void {
this.#securityState = securityState;
}
securityDetails(): Protocol.Network.SecurityDetails|null {
return this.#securityDetails;
}
securityOrigin(): string {
return this.#parsedURL.securityOrigin();
}
setSecurityDetails(securityDetails: Protocol.Network.SecurityDetails): void {
this.#securityDetails = securityDetails;
}
get startTime(): number {
return this.#startTime || -1;
}
setIssueTime(monotonicTime: number, wallTime: number): void {
this.#issueTime = monotonicTime;
this.#wallIssueTime = wallTime;
this.#startTime = monotonicTime;
}
issueTime(): number {
return this.#issueTime;
}
pseudoWallTime(monotonicTime: number): number {
return this.#wallIssueTime ? this.#wallIssueTime - this.#issueTime + monotonicTime : monotonicTime;
}
get responseReceivedTime(): number {
return this.#responseReceivedTime || -1;
}
set responseReceivedTime(x: number) {
this.#responseReceivedTime = x;
}
/**
* The time at which the returned response was generated. For cached
* responses, this is the last time the cache entry was validated.
*/
getResponseRetrievalTime(): Date|undefined {
return this.#responseRetrievalTime;
}
setResponseRetrievalTime(x: Date): void {
this.#responseRetrievalTime = x;
}
get endTime(): number {
return this.#endTime || -1;
}
set endTime(x: number) {
if (this.timing?.requestTime) {
// Check against accurate responseReceivedTime.
this.#endTime = Math.max(x, this.responseReceivedTime);
} else {
// Prefer endTime since it might be from the network stack.
this.#endTime = x;
if (this.#responseReceivedTime > x) {
this.#responseReceivedTime = x;
}
}
this.dispatchEventToListeners(Events.TIMING_CHANGED, this);
}
get duration(): number {
if (this.#endTime === -1 || this.#startTime === -1) {
return -1;
}
return this.#endTime - this.#startTime;
}
get latency(): number {
if (this.#responseReceivedTime === -1 || this.#startTime === -1) {
return -1;
}
return this.#responseReceivedTime - this.#startTime;
}
get resourceSize(): number {
return this.#resourceSize || 0;
}
set resourceSize(x: number) {
this.#resourceSize = x;
}
get transferSize(): number {
return this.#transferSize || 0;
}
increaseTransferSize(x: number): void {
this.#transferSize = (this.#transferSize || 0) + x;
}
setTransferSize(x: number): void {
this.#transferSize = x;
}
get finished(): boolean {
return this.#finished;
}
set finished(x: boolean) {
if (this.#finished === x) {
return;
}
this.#finished = x;
if (x) {
this.dispatchEventToListeners(Events.FINISHED_LOADING, this);
}
}
get failed(): boolean {
return this.#failed;
}
set failed(x: boolean) {
this.#failed = x;
}
get canceled(): boolean {
return this.#canceled;
}
set canceled(x: boolean) {
this.#canceled = x;
}
get preserved(): boolean {
return this.#preserved;
}
set preserved(x: boolean) {
this.#preserved = x;
}
blockedReason(): Protocol.Network.BlockedReason|undefined {
return this.#blockedReason;
}
setBlockedReason(reason: Protocol.Network.BlockedReason): void {
this.#blockedReason = reason;
}
corsErrorStatus(): Protocol.Network.CorsErrorStatus|undefined {
return this.#corsErrorStatus;
}
setCorsErrorStatus(corsErrorStatus: Protocol.Network.CorsErrorStatus): void {
this.#corsErrorStatus = corsErrorStatus;
}
wasBlocked(): boolean {
return Boolean(this.#blockedReason);
}
cached(): boolean {
return ((Boolean(this.#fromMemoryCache) || Boolean(this.#fromDiskCache)) && !this.#transferSize);
}
cachedInMemory(): boolean {
return Boolean(this.#fromMemoryCache) && !this.#transferSize;
}
fromPrefetchCache(): boolean {
return Boolean(this.#fromPrefetchCache);
}
setFromMemoryCache(): void {
this.#fromMemoryCache = true;
this.#timing = undefined;
}
get fromDiskCache(): boolean|undefined {
return this.#fromDiskCache;
}
setFromDiskCache(): void {
this.#fromDiskCache = true;
}
setFromPrefetchCache(): void {
this.#fromPrefetchCache = true;
}
fromEarlyHints(): boolean {
return Boolean(this.#fromEarlyHints);
}
setFromEarlyHints(): void {
this.#fromEarlyHints = true;
}
/**
* Returns true if the request was intercepted by a service worker and it
* provided its own response.
*/
get fetchedViaServiceWorker(): boolean {
return Boolean(this.#fetchedViaServiceWorker);
}
set fetchedViaServiceWorker(x: boolean) {
this.#fetchedViaServiceWorker = x;
}
get serviceWorkerRouterInfo():|Protocol.Network.ServiceWorkerRouterInfo|undefined {
return this.#serviceWorkerRouterInfo;
}
set serviceWorkerRouterInfo(x: Protocol.Network.ServiceWorkerRouterInfo) {
this.#serviceWorkerRouterInfo = x;
}
/**
* Returns true if the request was matched to a route when using the
* ServiceWorker static routing API.
*/
hasMatchingServiceWorkerRouter(): boolean {
// See definitions in `browser_protocol.pdl` for justification.
return (
this.#serviceWorkerRouterInfo !== undefined && this.serviceWorkerRouterInfo?.matchedSourceType !== undefined);
}
/**
* Returns true if the request was sent by a service worker.
*/
initiatedByServiceWorker(): boolean {
const networkManager = NetworkManager.forRequest(this);
if (!networkManager) {
return false;
}
return networkManager.target().type() === Type.ServiceWorker;
}
get timing(): Protocol.Network.ResourceTiming|undefined {
return this.#timing;
}
set timing(timingInfo: Protocol.Network.ResourceTiming|undefined) {
if (!timingInfo || this.#fromMemoryCache) {
return;
}
// Take startTime and responseReceivedTime from timing data for better accuracy.
// Timing's requestTime is a baseline in seconds, rest of the numbers there are ticks in millis.
this.#startTime = timingInfo.requestTime;
const headersReceivedTime = timingInfo.requestTime + timingInfo.receiveHeadersEnd / 1000.0;
if ((this.#responseReceivedTime || -1) < 0 || this.#responseReceivedTime > headersReceivedTime) {
this.#responseReceivedTime = headersReceivedTime;
}
if (this.#startTime > this.#responseReceivedTime) {
this.#responseReceivedTime = this.#startTime;
}
this.#timing = timingInfo;
this.dispatchEventToListeners(Events.TIMING_CHANGED, this);
}
private setConnectTimingFromExtraInfo(
connectTiming: Protocol.Network.ConnectTiming,
): void {
this.#startTime = connectTiming.requestTime;
this.dispatchEventToListeners(Events.TIMING_CHANGED, this);
}
get mimeType(): string {
return this.#mimeType;
}
set mimeType(x: string) {
this.#mimeType = x;
if (x === Platform.MimeType.MimeType.EVENTSTREAM && !this.#serverSentEvents) {
const parseFromStreamedData = this.resourceType() !== Common.ResourceType.resourceTypes.EventSource;
this.#serverSentEvents = new ServerSentEvents(
this,
parseFromStreamedData,
);
}
}
get displayName(): string {
return this.#parsedURL.displayName;
}
name(): string {
if (this.#name) {
return this.#name;
}
this.parseNameAndPathFromURL();
return this.#name as string;
}
path(): string {
if (this.#path) {
return this.#path;
}
this.parseNameAndPathFromURL();
return this.#path as string;
}
private parseNameAndPathFromURL(): void {
if (this.#parsedURL.isDataURL()) {
this.#name = this.#parsedURL.dataURLDisplayName();
this.#path = '';
} else if (this.#parsedURL.isBlobURL()) {
this.#name = this.#parsedURL.url;
this.#path = '';
} else if (this.#parsedURL.isAboutBlank()) {
this.#name = this.#parsedURL.url;
this.#path = '';
} else {
this.#path = this.#parsedURL.host + this.#parsedURL.folderPathComponents;
const networkManager = NetworkManager.forRequest(this);
const inspectedURL = networkManager ? Common.ParsedURL.ParsedURL.fromString(
networkManager.target().inspectedURL(),
) :
null;
this.#path = Platform.StringUtilities.trimURL(
this.#path,
inspectedURL ? inspectedURL.host : '',
);
if (this.#parsedURL.lastPathComponent || this.#parsedURL.queryParams) {
this.#name =
this.#parsedURL.lastPathComponent + (this.#parsedURL.queryParams ? '?' + this.#parsedURL.queryParams : '');
} else if (this.#parsedURL.folderPathComponents) {
this.#name = this.#parsedURL.folderPathComponents.substring(
this.#parsedURL.folderPathComponents.lastIndexOf('/') + 1,
) +
'/';
this.#path = this.#path.substring(
0,
this.#path.lastIndexOf('/'),
);
} else {
this.#name = this.#parsedURL.host;
this.#path = '';
}
}
}
get folder(): string {
let path: string = this.#parsedURL.path;
const indexOfQuery = path.indexOf('?');
if (indexOfQuery !== -1) {
path = path.substring(0, indexOfQuery);
}
const lastSlashIndex = path.lastIndexOf('/');
return lastSlashIndex !== -1 ? path.substring(0, lastSlashIndex) : '';
}
get pathname(): string {
return this.#parsedURL.path;
}
resourceType(): Common.ResourceType.ResourceType {
return this.#resourceType;
}
setResourceType(resourceType: Common.ResourceType.ResourceType): void {
this.#resourceType = resourceType;
}
get domain(): string {
return this.#parsedURL.host;
}
get scheme(): string {
return this.#parsedURL.scheme;
}
getInferredStatusText(): string {
return (this.statusText || HttpReasonPhraseStrings.getStatusText(this.statusCode));
}
redirectSource(): NetworkRequest|null {
return this.#redirectSource;
}
setRedirectSource(originatingRequest: NetworkRequest|null): void {
this.#redirectSource = originatingRequest;
}
preflightRequest(): NetworkRequest|null {
return this.#preflightRequest;
}
setPreflightRequest(preflightRequest: NetworkRequest|null): void {
this.#preflightRequest = preflightRequest;
}
preflightInitiatorRequest(): NetworkRequest|null {
return this.#preflightInitiatorRequest;
}
setPreflightInitiatorRequest(
preflightInitiatorRequest: NetworkRequest|null,
): void {
this.#preflightInitiatorRequest = preflightInitiatorRequest;
}
isPreflightRequest(): boolean {
return (
this.#initiator !== null && this.#initiator !== undefined &&
this.#initiator.type === Protocol.Network.InitiatorType.Preflight);
}
redirectDestination(): NetworkRequest|null {
return this.#redirectDestination;
}
setRedirectDestination(redirectDestination: NetworkRequest|null): void {
this.#redirectDestination = redirectDestination;
}
requestHeaders(): NameValue[] {
return this.#requestHeaders;
}
setRequestHeaders(headers: NameValue[]): void {
this.#requestHeaders = headers;
this.dispatchEventToListeners(Events.REQUEST_HEADERS_CHANGED);
}
requestHeadersText(): string|undefined {
return this.#requestHeadersText;
}
setRequestHeadersText(text: string): void {
this.#requestHeadersText = text;
this.dispatchEventToListeners(Events.REQUEST_HEADERS_CHANGED);
}
requestHeaderValue(headerName: string): string|undefined {
if (this.#requestHeaderValues[headerName]) {
return this.#requestHeaderValues[headerName];
}
this.#requestHeaderValues[headerName] = this.computeHeaderValue(
this.requestHeaders(),
headerName,
);
return this.#requestHeaderValues[headerName];
}
requestFormData(): Promise<string|null> {
if (!this.#requestFormDataPromise) {
this.#requestFormDataPromise = NetworkManager.requestPostData(this);
}
return this.#requestFormDataPromise;
}
setRequestFormData(hasData: boolean, data: string|null): void {
this.#requestFormDataPromise = hasData && data === null ? null : Promise.resolve(data);
this.#formParametersPromise = null;
}
private filteredProtocolName(): string {
const protocol = this.protocol.toLowerCase();
if (protocol === 'h2') {
return 'http/2.0';
}
return protocol.replace(/^http\/2(\.0)?\+/, 'http/2.0+');
}
requestHttpVersion(): string {
const headersText = this.requestHeadersText();
if (!headersText) {
const version = this.requestHeaderValue('version') || this.requestHeaderValue(':version');
if (version) {
return version;
}
return this.filteredProtocolName();
}
const firstLine = headersText.split(/\r\n/)[0];
const match = firstLine.match(/(HTTP\/\d+\.\d+)$/);
return match ? match[1] : 'HTTP/0.9';
}
get responseHeaders(): NameValue[] {
return this.#responseHeaders || [];
}
set responseHeaders(x: NameValue[]) {
this.#responseHeaders = x;
this.#sortedResponseHeaders = undefined;
this.#serverTimings = undefined;
this.#responseCookies = undefined;
this.#responseHeaderValues = {};
this.dispatchEventToListeners(Events.RESPONSE_HEADERS_CHANGED);
}
get earlyHintsHeaders(): NameValue[] {
return this.#earlyHintsHeaders || [];
}
set earlyHintsHeaders(x: NameValue[]) {
this.#earlyHintsHeaders = x;
}
get originalResponseHeaders(): Protocol.Fetch.HeaderEntry[] {
return this.#originalResponseHeaders;
}
set originalResponseHeaders(headers: Protocol.Fetch.HeaderEntry[]) {
this.#originalResponseHeaders = headers;
this.#sortedOriginalResponseHeaders = undefined;
}
get setCookieHeaders(): Protocol.Fetch.HeaderEntry[] {
return this.#setCookieHeaders;
}
set setCookieHeaders(headers: Protocol.Fetch.HeaderEntry[]) {
this.#setCookieHeaders = headers;
}
get responseHeadersText(): string {
return this.#responseHeadersText;
}
set responseHeadersText(x: string) {
this.#responseHeadersText = x;
this.dispatchEventToListeners(Events.RESPONSE_HEADERS_CHANGED);
}
get sortedResponseHeaders(): NameValue[] {
if (this.#sortedResponseHeaders !== undefined) {
return this.#sortedResponseHeaders;
}
this.#sortedResponseHeaders = this.responseHeaders.slice();
return this.#sortedResponseHeaders.sort(function(a, b) {
return Platform.StringUtilities.compare(
a.name.toLowerCase(),
b.name.toLowerCase(),
);
});
}
get sortedOriginalResponseHeaders(): NameValue[] {
if (this.#sortedOriginalResponseHeaders !== undefined) {
return this.#sortedOriginalResponseHeaders;
}
this.#sortedOriginalResponseHeaders = this.originalResponseHeaders.slice();
return this.#sortedOriginalResponseHeaders.sort(function(a, b) {
return Platform.StringUtilities.compare(
a.name.toLowerCase(),
b.name.toLowerCase(),
);
});
}
get overrideTypes(): OverrideType[] {
const types: OverrideType[] = [];
if (this.hasOverriddenContent) {
types.push('content');
}
if (this.hasOverriddenHeaders()) {
types.push('headers');
}
return types;
}
get hasOverriddenContent(): boolean {
return this.#hasOverriddenContent;
}
set hasOverriddenContent(value: boolean) {
this.#hasOverriddenContent = value;
}
#deduplicateHeaders(sortedHeaders: NameValue[]): NameValue[] {
const dedupedHeaders: NameValue[] = [];
for (const header of sortedHeaders) {
if (dedupedHeaders.length && dedupedHeaders[dedupedHeaders.length - 1].name === header.name) {
dedupedHeaders[dedupedHeaders.length - 1].value += `, ${header.value}`;
} else {
dedupedHeaders.push({name: header.name, value: header.value});
}
}
return dedupedHeaders;
}
hasOverriddenHeaders(): boolean {
if (!this.#originalResponseHeaders.length) {
return false;
}
const responseHeaders = this.#deduplicateHeaders(
this.sortedResponseHeaders,
);
const originalResponseHeaders = this.#deduplicateHeaders(
this.sortedOriginalResponseHeaders,
);
if (responseHeaders.length !== originalResponseHeaders.length) {
return true;
}
for (let i = 0; i < responseHeaders.length; i++) {
if (responseHeaders[i].name.toLowerCase() !== originalResponseHeaders[i].name.toLowerCase()) {
return true;
}
if (responseHeaders[i].value !== originalResponseHeaders[i].value) {
return true;
}
}
return false;
}
responseHeaderValue(headerName: string): string|undefined {
if (headerName in this.#responseHeaderValues) {
return this.#responseHeaderValues[headerName];
}
this.#responseHeaderValues[headerName] = this.computeHeaderValue(
this.responseHeaders,
headerName,
);
return this.#responseHeaderValues[headerName];
}
wasIntercepted(): boolean {
return this.#wasIntercepted;
}
setWasIntercepted(wasIntercepted: boolean): void {
this.#wasIntercepted = wasIntercepted;
}
setEarlyHintsHeaders(headers: NameValue[]): void {
this.earlyHintsHeaders = headers;
}
get responseCookies(): Cookie[] {
if (!this.#responseCookies) {
this.#responseCookies = CookieParser.parseSetCookie(
this.responseHeaderValue('Set-Cookie'),
this.domain,
) ||
[];
if (this.#responseCookiesPartitionKey) {
for (const cookie of this.#responseCookies) {
if (cookie.partitioned()) {
cookie.setPartitionKey(
this.#responseCookiesPartitionKey.topLevelSite,
this.#responseCookiesPartitionKey.hasCrossSiteAncestor,
);
}
}
} else if (this.#responseCookiesPartitionKeyOpaque) {
for (const cookie of this.#responseCookies) {
// Do not check cookie.partitioned() since most opaque partitions
// are fenced/credentialless frames partitioned by default.
cookie.setPartitionKeyOpaque();
}
}
}
return this.#responseCookies;
}
set responseCookies(responseCookies: Cookie[]) {
this.#responseCookies = responseCookies;
}
responseLastModified(): string|undefined {
return this.responseHeaderValue('last-modified');
}
allCookiesIncludingBlockedOnes(): Cookie[] {
return [
...this.includedRequestCookies().map(
includedRequestCookie => includedRequestCookie.cookie,
),
...this.responseCookies,
...this.blockedRequestCookies().map(
blockedRequestCookie => blockedRequestCookie.cookie,
),
...this.blockedResponseCookies().map(
blockedResponseCookie => blockedResponseCookie.cookie,
),
].filter(v => !!v);
}
get serverTimings(): ServerTiming[]|null {
if (typeof this.#serverTimings === 'undefined') {
this.#serverTimings = ServerTiming.parseHeaders(
this.responseHeaders,
);
}
return this.#serverTimings;
}
queryString(): string|null {
if (this.#queryString !== undefined) {
return this.#queryString;
}
let queryString: string|null = null;
const url = this.url();
const questionMarkPosition = url.indexOf('?');
if (questionMarkPosition !== -1) {
queryString = url.substring(questionMarkPosition + 1);
const hashSignPosition = queryString.indexOf('#');
if (hashSignPosition !== -1) {
queryString = queryString.substring(0, hashSignPosition);
}
}
this.#queryString = queryString;
return this.#queryString;
}
get queryParameters(): NameValue[]|null {
if (this.#parsedQueryParameters) {
return this.#parsedQueryParameters;
}
const queryString = this.queryString();
if (!queryString) {
return null;
}
this.#parsedQueryParameters = this.parseParameters(queryString);
return this.#parsedQueryParameters;
}
private async parseFormParameters(): Promise<NameValue[]|null> {
const requestContentType = this.requestContentType();
if (!requestContentType) {
return null;
}
// Handling application/#x-www-form-urlencoded request bodies.
if (requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) {
const formData = await this.requestFormData();
if (!formData) {
return null;
}
return this.parseParameters(formData);
}
// Handling multipart/form-data request bodies.
const multipartDetails = requestContentType.match(
/^multipart\/form-data\s*;\s*boundary\s*=\s*(\S+)\s*$/,
);
if (!multipartDetails) {
return null;
}
const boundary = multipartDetails[1];
if (!boundary) {
return null;
}
const formData = await this.requestFormData();
if (!formData) {
return null;
}
return this.parseMultipartFormDataParameters(formData, boundary);
}
formParameters(): Promise<NameValue[]|null> {
if (!this.#formParametersPromise) {
this.#formParametersPromise = this.parseFormParameters();
}
return this.#formParametersPromise;
}
responseHttpVersion(): string {
const headersText = this.#responseHeadersText;
if (!headersText) {
const version = this.responseHeaderValue('version') || this.responseHeaderValue(':version');
if (version) {
return version;
}
return this.filteredProtocolName();
}
const firstLine = headersText.split(/\r\n/)[0];
const match = firstLine.match(/^(HTTP\/\d+\.\d+)/);
return match ? match[1] : 'HTTP/0.9';
}
private parseParameters(queryString: string): NameValue[] {
function parseNameValue(pair: string): {name: string, value: string} {
const position = pair.indexOf('=');
if (position === -1) {
return {name: pair, value: ''};
}
return {
name: pair.substring(0, position),
value: pair.substring(position + 1),
};
}
return queryString.split('&').map(parseNameValue);
}
/**
* Parses multipart/form-data; boundary=boundaryString request bodies -
* --boundaryString
* Content-Disposition: form-data; #name="field-#name"; filename="r.gif"
* Content-Type: application/octet-stream
*
* optionalValue
* --boundaryString
* Content-Disposition: form-data; #name="field-#name-2"
*
* optionalValue2
* --boundaryString--
*/
private parseMultipartFormDataParameters(
data: string,
boundary: string,
): NameValue[] {
const sanitizedBoundary = Platform.StringUtilities.escapeForRegExp(boundary);
const keyValuePattern = new RegExp(
// Header with an optional file #name.
'^\\r\\ncontent-disposition\\s*:\\s*form-data\\s*;\\s*name="([^"]*)"(?:\\s*;\\s*filename="([^"]*)")?' +
// Optional secondary header with the content type.
'(?:\\r\\ncontent-type\\s*:\\s*([^\\r\\n]*))?' +
// Padding.
'\\r\\n\\r\\n' +
// Value
'(.*)' +
// Padding.
'\\r\\n$',
'is',
);
const fields = data.split(
new RegExp(`--${sanitizedBoundary}(?:--\s*$)?`, 'g'),
);
return fields.reduce(parseMultipartField, []);
function parseMultipartField(
result: NameValue[],
field: string,
): NameValue[] {
const [match, name, filename, contentType, value] = field.match(keyValuePattern) || [];
if (!match) {
return result;
}
const processedValue = filename || contentType ? i18nString(UIStrings.binary) : value;
result.push({name, value: processedValue});
return result;
}
}
private computeHeaderValue(
headers: NameValue[],
headerName: string,
): string|undefined {
headerName = headerName.toLowerCase();
const values = [];
for (let i = 0; i < headers.length; ++i) {
if (headers[i].name.toLowerCase() === headerName) {
values.push(headers[i].value);
}
}
if (!values.length) {
return undefined;
}
// Set-Cookie #values should be separated by '\n', not comma, otherwise cookies could not be parsed.
if (headerName === 'set-cookie') {
return values.join('\n');
}
return values.join(', ');
}
requestContentData(): Promise<TextUtils.ContentData.ContentDataOrError> {
if (this.#contentData) {
return this.#contentData;
}
if (this.#contentDataProvider) {
this.#contentData = this.#contentDataProvider();
} else {
this.#contentData = NetworkManager.requestContentData(this);
}
return this.#contentData;
}
setContentDataProvider(
dataProvider: () => P