// 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 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 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;
  #renderBlockingBehavior?: Protocol.Network.RenderBlockingBehavior;
  #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;
  #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;
  #deviceBoundSessionUsages: Protocol.Network.DeviceBoundSessionWithUsage[] = [];
  #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[] = [];
  #isAdRelated: boolean;
  #appliedNetworkConditionsId?: string;

  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.#isAdRelated = 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;
  }

  get appliedNetworkConditionsId(): string|undefined {
    return this.#appliedNetworkConditionsId;
  }

  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;
  }

  setRenderBlockingBehavior(renderBlocking: Protocol.Network.RenderBlockingBehavior): void {
    this.#renderBlockingBehavior = renderBlocking;
  }

  renderBlockingBehavior(): Protocol.Network.RenderBlockingBehavior|undefined {
    return this.#renderBlockingBehavior;
  }

  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: () => Promise<TextUtils.ContentData.ContentDataOrError>,
      ): void {
    console.assert(
        !this.#contentData,
        'contentData can only be set once.',
    );
    this.#contentDataProvider = dataProvider;
  }

  requestStreamingContent(): Promise<TextUtils.StreamingContentData.StreamingContentDataOrError> {
    if (this.#streamingContentData) {
      return this.#streamingContentData;
    }

    const contentPromise = this.finished ? this.requestContentData() : NetworkManager.streamResponseBody(this);
    this.#streamingContentData = contentPromise.then(contentData => {
      if (TextUtils.ContentData.ContentData.isError(contentData)) {
        return contentData;
      }
      // Note that this is save: "streamResponseBody()" always creates base64-based ContentData and
      // for "contentData()" we'll never call "addChunk".
      return TextUtils.StreamingContentData.StreamingContentData.from(
          contentData,
      );
    });

    return this.#streamingContentData;
  }

  contentURL(): Platform.DevToolsPath.UrlString {
    return this.#url;
  }

  contentType(): Common.ResourceType.ResourceType {
    return this.#resourceType;
  }

  async searchInContent(
      query: string,
      caseSensitive: boolean,
      isRegex: boolean,
      ): Promise<TextUtils.ContentProvider.SearchMatch[]> {
    if (!this.#contentDataProvider) {
      return await NetworkManager.searchInRequest(
          this,
          query,
          caseSensitive,
          isRegex,
      );
    }

    const contentData = await this.requestContentData();
    if (TextUtils.ContentData.ContentData.isError(contentData) || !contentData.isTextContent) {
      return [];
    }
    return TextUtils.TextUtils.performSearchInContentData(
        contentData,
        query,
        caseSensitive,
        isRegex,
    );
  }

  requestContentType(): string|undefined {
    return this.requestHeaderValue('Content-Type');
  }

  requestContentEncoding(): string|undefined {
    return this.requestHeaderValue('Content-Encoding');
  }

  hasErrorStatusCode(): boolean {
    return this.statusCode >= 400;
  }

  setInitialPriority(priority: Protocol.Network.ResourcePriority): void {
    this.#initialPriority = priority;
  }

  initialPriority(): Protocol.Network.ResourcePriority|null {
    return this.#initialPriority;
  }

  setPriority(priority: Protocol.Network.ResourcePriority): void {
    this.#currentPriority = priority;
  }

  priority(): Protocol.Network.ResourcePriority|null {
    return this.#currentPriority || this.#initialPriority || null;
  }

  setSignedExchangeInfo(info: Protocol.Network.SignedExchangeInfo): void {
    this.#signedExchangeInfo = info;
  }

  signedExchangeInfo(): Protocol.Network.SignedExchangeInfo|null {
    return this.#signedExchangeInfo;
  }

  async populateImageSource(image: HTMLImageElement): Promise<void> {
    const contentData = await this.requestContentData();
    if (TextUtils.ContentData.ContentData.isError(contentData)) {
      return;
    }
    let imageSrc = contentData.asDataUrl();
    if (imageSrc === null && !this.#failed) {
      const cacheControl = this.responseHeaderValue('cache-control') || '';
      if (!cacheControl.includes('no-cache')) {
        imageSrc = this.#url;
      }
    }
    if (imageSrc !== null) {
      image.src = imageSrc;
    }
  }

  initiator(): Protocol.Network.Initiator|null {
    return this.#initiator || null;
  }

  hasUserGesture(): boolean|null {
    return this.#hasUserGesture ?? null;
  }

  frames(): WebSocketFrame[] {
    return this.#frames;
  }

  addProtocolFrameError(errorMessage: string, time: number): void {
    this.addFrame({
      type: WebSocketFrameType.Error,
      text: errorMessage,
      time: this.pseudoWallTime(time),
      opCode: -1,
      mask: false,
    });
  }

  addProtocolFrame(
      response: Protocol.Network.WebSocketFrame,
      time: number,
      sent: boolean,
      ): void {
    const type = sent ? WebSocketFrameType.Send : WebSocketFrameType.Receive;
    this.addFrame({
      type,
      text: response.payloadData,
      time: this.pseudoWallTime(time),
      opCode: response.opcode,
      mask: response.mask,
    });
  }

  addFrame(frame: WebSocketFrame): void {
    this.#frames.push(frame);
    this.dispatchEventToListeners(Events.WEBSOCKET_FRAME_ADDED, frame);
  }

  directSocketChunks(): DirectSocketChunk[] {
    return this.#directSocketChunks;
  }

  addDirectSocketChunk(chunk: DirectSocketChunk): void {
    this.#directSocketChunks.push(chunk);
    this.dispatchEventToListeners(Events.DIRECTSOCKET_CHUNK_ADDED, chunk);
  }

  eventSourceMessages(): readonly EventSourceMessage[] {
    return this.#serverSentEvents?.eventSourceMessages ?? [];
  }

  addEventSourceMessage(
      time: number,
      eventName: string,
      eventId: string,
      data: string,
      ): void {
    this.#serverSentEvents?.onProtocolEventSourceMessageReceived(
        eventName,
        data,
        eventId,
        this.pseudoWallTime(time),
    );
  }

  markAsRedirect(redirectCount: number): void {
    this.#isRedirect = true;
    this.#requestId = `${this.#backendRequestId}:redirected.${redirectCount}`;
  }

  isRedirect(): boolean {
    return this.#isRedirect;
  }

  setRequestIdForTest(requestId: Protocol.Network.RequestId): void {
    this.#backendRequestId = requestId;
    this.#requestId = requestId;
  }

  charset(): string|null {
    return this.#charset ?? null;
  }

  setCharset(charset: string): void {
    this.#charset = charset;
  }

  addExtraRequestInfo(extraRequestInfo: ExtraRequestInfo): void {
    this.#blockedRequestCookies = extraRequestInfo.blockedRequestCookies;
    this.setIncludedRequestCookies(extraRequestInfo.includedRequestCookies);
    this.setRequestHeaders(extraRequestInfo.requestHeaders);
    this.#hasExtraRequestInfo = true;
    this.setRequestHeadersText('');  // Mark request headers as non-provisional
    this.#deviceBoundSessionUsages = extraRequestInfo.deviceBoundSessionUsages || [];
    this.#clientSecurityState = extraRequestInfo.clientSecurityState;
    this.#appliedNetworkConditionsId = extraRequestInfo.appliedNetworkConditionsId;
    if (extraRequestInfo.connectTiming) {
      this.setConnectTimingFromExtraInfo(extraRequestInfo.connectTiming);
    }
    this.#siteHasCookieInOtherPartition = extraRequestInfo.siteHasCookieInOtherPartition ?? false;

    this.#hasThirdPartyCookiePhaseoutIssue = this.#blockedRequestCookies.some(
        item => item.blockedReasons.includes(
            Protocol.Network.CookieBlockedReason.ThirdPartyPhaseout,
            ),
    );
  }

  setAppliedNetworkConditions(appliedNetworkConditionsId: string): void {
    this.#appliedNetworkConditionsId = appliedNetworkConditionsId;
  }

  getDeviceBoundSessionUsages(): Protocol.Network.DeviceBoundSessionWithUsage[] {
    return this.#deviceBoundSessionUsages;
  }

  hasExtraRequestInfo(): boolean {
    return this.#hasExtraRequestInfo;
  }

  blockedRequestCookies(): BlockedCookieWithReason[] {
    return this.#blockedRequestCookies;
  }

  setIncludedRequestCookies(includedRequestCookies: IncludedCookieWithReason[]): void {
    this.#includedRequestCookies = includedRequestCookies;
  }

  includedRequestCookies(): IncludedCookieWithReason[] {
    return this.#includedRequestCookies;
  }

  hasRequestCookies(): boolean {
    return (this.#includedRequestCookies.length > 0 || this.#blockedRequestCookies.length > 0);
  }

  siteHasCookieInOtherPartition(): boolean {
    return this.#siteHasCookieInOtherPartition;
  }

  // Parse the status text from the first line of the response headers text.
  // See net::HttpResponseHeaders::GetStatusText.
  static parseStatusTextFromResponseHeadersText(
      responseHeadersText: string,
      ): string {
    const firstLineParts = responseHeadersText.split('\r')[0].split(' ');
    return firstLineParts.slice(2).join(' ');
  }

  addExtraResponseInfo(extraResponseInfo: ExtraResponseInfo): void {
    this.#blockedResponseCookies = extraResponseInfo.blockedResponseCookies;
    if (extraResponseInfo.exemptedResponseCookies) {
      this.#exemptedResponseCookies = extraResponseInfo.exemptedResponseCookies;
    }
    this.#responseCookiesPartitionKey =
        extraResponseInfo.cookiePartitionKey ? extraResponseInfo.cookiePartitionKey : null;
    this.#responseCookiesPartitionKeyOpaque = extraResponseInfo.cookiePartitionKeyOpaque || null;
    this.responseHeaders = extraResponseInfo.responseHeaders;
    // We store a copy of the headers we initially received, so that after
    // potential header overrides, we can compare actual with original headers.
    this.originalResponseHeaders = extraResponseInfo.responseHeaders.map(
        headerEntry => ({...headerEntry}),
    );

    if (extraResponseInfo.responseHeadersText) {
      this.responseHeadersText = extraResponseInfo.responseHeadersText;

      if (!this.requestHeadersText()) {
        // Generate request headers text from raw headers in extra request info because
        // Network.requestWillBeSentExtraInfo doesn't include headers text.
        let requestHeadersText = `${this.requestMethod} ${this.parsedURL.path}`;
        if (this.parsedURL.queryParams) {
          requestHeadersText += `?${this.parsedURL.queryParams}`;
        }
        requestHeadersText += ' HTTP/1.1\r\n';

        for (const {name, value} of this.requestHeaders()) {
          requestHeadersText += `${name}: ${value}\r\n`;
        }
        this.setRequestHeadersText(requestHeadersText);
      }

      this.statusText = NetworkRequest.parseStatusTextFromResponseHeadersText(
          extraResponseInfo.responseHeadersText,
      );
    }
    this.#remoteAddressSpace = extraResponseInfo.resourceIPAddressSpace;

    if (extraResponseInfo.statusCode) {
      this.statusCode = extraResponseInfo.statusCode;
    }

    this.#hasExtraResponseInfo = true;

    // TODO(crbug.com/1252463) Explore replacing this with a DevTools Issue.
    const networkManager = NetworkManager.forRequest(this);
    if (!networkManager) {
      return;
    }
    for (const blockedCookie of this.#blockedResponseCookies) {
      if (blockedCookie.blockedReasons.includes(
              Protocol.Network.SetCookieBlockedReason.NameValuePairExceedsMaxSize,
              )) {
        const message = i18nString(UIStrings.setcookieHeaderIsIgnoredIn, {
          PH1: this.url(),
        });
        networkManager.dispatchEventToListeners(
            NetworkManagerEvents.MessageGenerated,
            {message, requestId: this.#requestId, warning: true},
        );
      }
    }

    const cookieModel = networkManager.target().model(CookieModel);
    if (!cookieModel) {
      return;
    }
    for (const exemptedCookie of this.#exemptedResponseCookies) {
      cookieModel.removeBlockedCookie(exemptedCookie.cookie);
    }
    for (const blockedCookie of this.#blockedResponseCookies) {
      const cookie = blockedCookie.cookie;
      if (!cookie) {
        continue;
      }
      if (blockedCookie.blockedReasons.includes(
              Protocol.Network.SetCookieBlockedReason.ThirdPartyPhaseout,
              )) {
        this.#hasThirdPartyCookiePhaseoutIssue = true;
      }
      cookieModel.addBlockedCookie(
          cookie,
          blockedCookie.blockedReasons.map(blockedReason => ({
                                             attribute: setCookieBlockedReasonToAttribute(blockedReason),
                                             uiString: setCookieBlockedReasonToUiString(blockedReason),
                                           })),
      );
    }
  }

  hasExtraResponseInfo(): boolean {
    return this.#hasExtraResponseInfo;
  }

  blockedResponseCookies(): BlockedSetCookieWithReason[] {
    return this.#blockedResponseCookies;
  }

  exemptedResponseCookies(): ExemptedSetCookieWithReason[] {
    return this.#exemptedResponseCookies;
  }

  nonBlockedResponseCookies(): Cookie[] {
    const blockedCookieLines: Array<string|null> = this.blockedResponseCookies().map(
        blockedCookie => blockedCookie.cookieLine,
    );
    // Use array and remove 1 by 1 to handle the (potential) case of multiple
    // identical cookies, only some of which are blocked.
    const responseCookies = this.responseCookies.filter(cookie => {
      const index = blockedCookieLines.indexOf(cookie.getCookieLine());
      if (index !== -1) {
        blockedCookieLines[index] = null;
        return false;
      }
      return true;
    });
    return responseCookies;
  }

  responseCookiesPartitionKey(): Protocol.Network.CookiePartitionKey|null {
    return this.#responseCookiesPartitionKey;
  }

  responseCookiesPartitionKeyOpaque(): boolean|null {
    return this.#responseCookiesPartitionKeyOpaque;
  }

  redirectSourceSignedExchangeInfoHasNoErrors(): boolean {
    return (
        this.#redirectSource !== null && this.#redirectSource.#signedExchangeInfo !== null &&
        !this.#redirectSource.#signedExchangeInfo.errors);
  }

  clientSecurityState(): Protocol.Network.ClientSecurityState|undefined {
    return this.#clientSecurityState;
  }

  setTrustTokenParams(
      trustTokenParams: Protocol.Network.TrustTokenParams,
      ): void {
    this.#trustTokenParams = trustTokenParams;
  }

  trustTokenParams(): Protocol.Network.TrustTokenParams|undefined {
    return this.#trustTokenParams;
  }

  setTrustTokenOperationDoneEvent(
      doneEvent: Protocol.Network.TrustTokenOperationDoneEvent,
      ): void {
    this.#trustTokenOperationDoneEvent = doneEvent;

    this.dispatchEventToListeners(Events.TRUST_TOKEN_RESULT_ADDED);
  }

  trustTokenOperationDoneEvent():|Protocol.Network.TrustTokenOperationDoneEvent|undefined {
    return this.#trustTokenOperationDoneEvent;
  }

  setIsSameSite(isSameSite: boolean): void {
    this.#isSameSite = isSameSite;
  }

  isSameSite(): boolean|null {
    return this.#isSameSite;
  }

  setIsAdRelated(isAdRelated: boolean): void {
    this.#isAdRelated = isAdRelated;
  }

  isAdRelated(): boolean {
    return this.#isAdRelated;
  }

  getAssociatedData(key: string): object|null {
    return this.#associatedData.get(key) || null;
  }

  setAssociatedData(key: string, data: object): void {
    this.#associatedData.set(key, data);
  }

  deleteAssociatedData(key: string): void {
    this.#associatedData.delete(key);
  }

  hasThirdPartyCookiePhaseoutIssue(): boolean {
    return this.#hasThirdPartyCookiePhaseoutIssue;
  }

  addDataReceivedEvent({
    timestamp,
    dataLength,
    encodedDataLength,
    data,
  }: Protocol.Network.DataReceivedEvent): void {
    this.resourceSize += dataLength;
    if (encodedDataLength !== -1) {
      this.increaseTransferSize(encodedDataLength);
    }
    this.endTime = timestamp;
    if (data) {
      void this.#streamingContentData?.then(contentData => {
        if (!TextUtils.StreamingContentData.isError(contentData)) {
          contentData.addChunk(data);
        }
      });
    }
  }

  waitForResponseReceived(): Promise<void> {
    if (this.responseReceivedPromise) {
      return this.responseReceivedPromise;
    }
    const {promise, resolve} = Promise.withResolvers<void>();
    this.responseReceivedPromise = promise;
    this.responseReceivedPromiseResolve = resolve;
    return this.responseReceivedPromise;
  }
}

export enum Events {
  FINISHED_LOADING = 'FinishedLoading',
  TIMING_CHANGED = 'TimingChanged',
  REMOTE_ADDRESS_CHANGED = 'RemoteAddressChanged',
  REQUEST_HEADERS_CHANGED = 'RequestHeadersChanged',
  RESPONSE_HEADERS_CHANGED = 'ResponseHeadersChanged',
  WEBSOCKET_FRAME_ADDED = 'WebsocketFrameAdded',
  DIRECTSOCKET_CHUNK_ADDED = 'DirectsocketChunkAdded',
  EVENT_SOURCE_MESSAGE_ADDED = 'EventSourceMessageAdded',
  TRUST_TOKEN_RESULT_ADDED = 'TrustTokenResultAdded',
}

export interface EventTypes {
  [Events.FINISHED_LOADING]: NetworkRequest;
  [Events.TIMING_CHANGED]: NetworkRequest;
  [Events.REMOTE_ADDRESS_CHANGED]: NetworkRequest;
  [Events.REQUEST_HEADERS_CHANGED]: void;
  [Events.RESPONSE_HEADERS_CHANGED]: void;
  [Events.WEBSOCKET_FRAME_ADDED]: WebSocketFrame;
  [Events.DIRECTSOCKET_CHUNK_ADDED]: DirectSocketChunk;
  [Events.DIRECTSOCKET_CHUNK_ADDED]: DirectSocketChunk;
  [Events.EVENT_SOURCE_MESSAGE_ADDED]: EventSourceMessage;
  [Events.TRUST_TOKEN_RESULT_ADDED]: void;
}

export const enum InitiatorType {
  OTHER = 'other',
  PARSER = 'parser',
  REDIRECT = 'redirect',
  SCRIPT = 'script',
  PRELOAD = 'preload',
  SIGNED_EXCHANGE = 'signedExchange',
  PREFLIGHT = 'preflight',
}

export enum WebSocketFrameType {
  /* eslint-disable @typescript-eslint/naming-convention -- Used by web_tests. */
  Send = 'send',
  Receive = 'receive',
  Error = 'error',
  /* eslint-enable @typescript-eslint/naming-convention */
}

export const cookieExemptionReasonToUiString = function(
    exemptionReason: Protocol.Network.CookieExemptionReason,
    ): string {
  switch (exemptionReason) {
    case Protocol.Network.CookieExemptionReason.UserSetting:
      return i18nString(UIStrings.exemptionReasonUserSetting);
    case Protocol.Network.CookieExemptionReason.TPCDMetadata:
      return i18nString(UIStrings.exemptionReasonTPCDMetadata);
    case Protocol.Network.CookieExemptionReason.TopLevelTPCDDeprecationTrial:
      return i18nString(UIStrings.exemptionReasonTopLevelTPCDDeprecationTrial);
    case Protocol.Network.CookieExemptionReason.TPCDDeprecationTrial:
      return i18nString(UIStrings.exemptionReasonTPCDDeprecationTrial);
    case Protocol.Network.CookieExemptionReason.TPCDHeuristics:
      return i18nString(UIStrings.exemptionReasonTPCDHeuristics);
    case Protocol.Network.CookieExemptionReason.EnterprisePolicy:
      return i18nString(UIStrings.exemptionReasonEnterprisePolicy);
    case Protocol.Network.CookieExemptionReason.StorageAccess:
      return i18nString(UIStrings.exemptionReasonStorageAccessAPI);
    case Protocol.Network.CookieExemptionReason.TopLevelStorageAccess:
      return i18nString(UIStrings.exemptionReasonTopLevelStorageAccessAPI);
    case Protocol.Network.CookieExemptionReason.Scheme:
      return i18nString(UIStrings.exemptionReasonScheme);
  }
  return '';
};

export const cookieBlockedReasonToUiString = function(
    blockedReason: Protocol.Network.CookieBlockedReason,
    ): string {
  switch (blockedReason) {
    case Protocol.Network.CookieBlockedReason.SecureOnly:
      return i18nString(UIStrings.secureOnly);
    case Protocol.Network.CookieBlockedReason.NotOnPath:
      return i18nString(UIStrings.notOnPath);
    case Protocol.Network.CookieBlockedReason.DomainMismatch:
      return i18nString(UIStrings.domainMismatch);
    case Protocol.Network.CookieBlockedReason.SameSiteStrict:
      return i18nString(UIStrings.sameSiteStrict);
    case Protocol.Network.CookieBlockedReason.SameSiteLax:
      return i18nString(UIStrings.sameSiteLax);
    case Protocol.Network.CookieBlockedReason.SameSiteUnspecifiedTreatedAsLax:
      return i18nString(UIStrings.sameSiteUnspecifiedTreatedAsLax);
    case Protocol.Network.CookieBlockedReason.SameSiteNoneInsecure:
      return i18nString(UIStrings.sameSiteNoneInsecure);
    case Protocol.Network.CookieBlockedReason.UserPreferences:
      return i18nString(UIStrings.userPreferences);
    case Protocol.Network.CookieBlockedReason.UnknownError:
      return i18nString(UIStrings.unknownError);
    case Protocol.Network.CookieBlockedReason.SchemefulSameSiteStrict:
      return i18nString(UIStrings.schemefulSameSiteStrict);
    case Protocol.Network.CookieBlockedReason.SchemefulSameSiteLax:
      return i18nString(UIStrings.schemefulSameSiteLax);
    case Protocol.Network.CookieBlockedReason.SchemefulSameSiteUnspecifiedTreatedAsLax:
      return i18nString(UIStrings.schemefulSameSiteUnspecifiedTreatedAsLax);
    case Protocol.Network.CookieBlockedReason.NameValuePairExceedsMaxSize:
      return i18nString(UIStrings.nameValuePairExceedsMaxSize);
    case Protocol.Network.CookieBlockedReason.ThirdPartyPhaseout:
      return i18nString(UIStrings.thirdPartyPhaseout);
  }
  return '';
};

export const setCookieBlockedReasonToUiString = function(
    blockedReason: Protocol.Network.SetCookieBlockedReason,
    ): string {
  switch (blockedReason) {
    case Protocol.Network.SetCookieBlockedReason.SecureOnly:
      return i18nString(UIStrings.blockedReasonSecureOnly);
    case Protocol.Network.SetCookieBlockedReason.SameSiteStrict:
      return i18nString(UIStrings.blockedReasonSameSiteStrictLax, {
        PH1: 'SameSite=Strict',
      });
    case Protocol.Network.SetCookieBlockedReason.SameSiteLax:
      return i18nString(UIStrings.blockedReasonSameSiteStrictLax, {
        PH1: 'SameSite=Lax',
      });
    case Protocol.Network.SetCookieBlockedReason.SameSiteUnspecifiedTreatedAsLax:
      return i18nString(UIStrings.blockedReasonSameSiteUnspecifiedTreatedAsLax);
    case Protocol.Network.SetCookieBlockedReason.SameSiteNoneInsecure:
      return i18nString(UIStrings.blockedReasonSameSiteNoneInsecure);
    case Protocol.Network.SetCookieBlockedReason.UserPreferences:
      return i18nString(UIStrings.thisSetcookieWasBlockedDueToUser);
    case Protocol.Network.SetCookieBlockedReason.SyntaxError:
      return i18nString(UIStrings.thisSetcookieHadInvalidSyntax);
    case Protocol.Network.SetCookieBlockedReason.SchemeNotSupported:
      return i18nString(UIStrings.theSchemeOfThisConnectionIsNot);
    case Protocol.Network.SetCookieBlockedReason.OverwriteSecure:
      return i18nString(UIStrings.blockedReasonOverwriteSecure);
    case Protocol.Network.SetCookieBlockedReason.InvalidDomain:
      return i18nString(UIStrings.blockedReasonInvalidDomain);
    case Protocol.Network.SetCookieBlockedReason.InvalidPrefix:
      return i18nString(UIStrings.blockedReasonInvalidPrefix);
    case Protocol.Network.SetCookieBlockedReason.UnknownError:
      return i18nString(UIStrings.anUnknownErrorWasEncounteredWhenTrying);
    case Protocol.Network.SetCookieBlockedReason.SchemefulSameSiteStrict:
      return i18nString(
          UIStrings.thisSetcookieWasBlockedBecauseItHadTheSamesiteStrictLax,
          {PH1: 'SameSite=Strict'},
      );
    case Protocol.Network.SetCookieBlockedReason.SchemefulSameSiteLax:
      return i18nString(
          UIStrings.thisSetcookieWasBlockedBecauseItHadTheSamesiteStrictLax,
          {PH1: 'SameSite=Lax'},
      );
    case Protocol.Network.SetCookieBlockedReason.SchemefulSameSiteUnspecifiedTreatedAsLax:
      return i18nString(UIStrings.thisSetcookieDidntSpecifyASamesite);
    case Protocol.Network.SetCookieBlockedReason.NameValuePairExceedsMaxSize:
      return i18nString(
          UIStrings.thisSetcookieWasBlockedBecauseTheNameValuePairExceedsMaxSize,
      );
    case Protocol.Network.SetCookieBlockedReason.DisallowedCharacter:
      return i18nString(UIStrings.thisSetcookieHadADisallowedCharacter);
    case Protocol.Network.SetCookieBlockedReason.ThirdPartyPhaseout:
      return i18nString(UIStrings.thisSetcookieWasBlockedDueThirdPartyPhaseout);
  }
  return '';
};

export const cookieBlockedReasonToAttribute = function(
    blockedReason: Protocol.Network.CookieBlockedReason,
    ): Attribute|null {
  switch (blockedReason) {
    case Protocol.Network.CookieBlockedReason.SecureOnly:
      return Attribute.SECURE;
    case Protocol.Network.CookieBlockedReason.NotOnPath:
      return Attribute.PATH;
    case Protocol.Network.CookieBlockedReason.DomainMismatch:
      return Attribute.DOMAIN;
    case Protocol.Network.CookieBlockedReason.SameSiteStrict:
    case Protocol.Network.CookieBlockedReason.SameSiteLax:
    case Protocol.Network.CookieBlockedReason.SameSiteUnspecifiedTreatedAsLax:
    case Protocol.Network.CookieBlockedReason.SameSiteNoneInsecure:
    case Protocol.Network.CookieBlockedReason.SchemefulSameSiteStrict:
    case Protocol.Network.CookieBlockedReason.SchemefulSameSiteLax:
    case Protocol.Network.CookieBlockedReason.SchemefulSameSiteUnspecifiedTreatedAsLax:
      return Attribute.SAME_SITE;
    case Protocol.Network.CookieBlockedReason.NameValuePairExceedsMaxSize:
    case Protocol.Network.CookieBlockedReason.UserPreferences:
    case Protocol.Network.CookieBlockedReason.ThirdPartyPhaseout:
    case Protocol.Network.CookieBlockedReason.UnknownError:
      return null;
  }
  return null;
};

export const setCookieBlockedReasonToAttribute = function(
    blockedReason: Protocol.Network.SetCookieBlockedReason,
    ): Attribute|null {
  switch (blockedReason) {
    case Protocol.Network.SetCookieBlockedReason.SecureOnly:
    case Protocol.Network.SetCookieBlockedReason.OverwriteSecure:
      return Attribute.SECURE;
    case Protocol.Network.SetCookieBlockedReason.SameSiteStrict:
    case Protocol.Network.SetCookieBlockedReason.SameSiteLax:
    case Protocol.Network.SetCookieBlockedReason.SameSiteUnspecifiedTreatedAsLax:
    case Protocol.Network.SetCookieBlockedReason.SameSiteNoneInsecure:
    case Protocol.Network.SetCookieBlockedReason.SchemefulSameSiteStrict:
    case Protocol.Network.SetCookieBlockedReason.SchemefulSameSiteLax:
    case Protocol.Network.SetCookieBlockedReason.SchemefulSameSiteUnspecifiedTreatedAsLax:
      return Attribute.SAME_SITE;
    case Protocol.Network.SetCookieBlockedReason.InvalidDomain:
      return Attribute.DOMAIN;
    case Protocol.Network.SetCookieBlockedReason.InvalidPrefix:
      return Attribute.NAME;
    case Protocol.Network.SetCookieBlockedReason.NameValuePairExceedsMaxSize:
    case Protocol.Network.SetCookieBlockedReason.UserPreferences:
    case Protocol.Network.SetCookieBlockedReason.ThirdPartyPhaseout:
    case Protocol.Network.SetCookieBlockedReason.SyntaxError:
    case Protocol.Network.SetCookieBlockedReason.SchemeNotSupported:
    case Protocol.Network.SetCookieBlockedReason.UnknownError:
    case Protocol.Network.SetCookieBlockedReason.DisallowedCharacter:
      return null;
  }
  return null;
};

export interface NameValue {
  name: string;
  value: string;
}

export interface WebSocketFrame {
  type: WebSocketFrameType;
  time: number;
  text: string;
  opCode: number;
  mask: boolean;
}

export interface BlockedSetCookieWithReason {
  blockedReasons: Protocol.Network.SetCookieBlockedReason[];
  cookieLine: string;
  cookie: Cookie|null;
}

export interface BlockedCookieWithReason {
  cookie: Cookie;
  blockedReasons: Protocol.Network.CookieBlockedReason[];
}

export interface IncludedCookieWithReason {
  cookie: Cookie;
  exemptionReason?: Protocol.Network.CookieExemptionReason;
}

export interface ExemptedSetCookieWithReason {
  cookie: Cookie;
  cookieLine: string;
  exemptionReason: Protocol.Network.CookieExemptionReason;
}

export interface EventSourceMessage {
  time: number;
  eventName: string;
  eventId: string;
  data: string;
}

export interface ExtraRequestInfo {
  blockedRequestCookies: Array<{blockedReasons: Protocol.Network.CookieBlockedReason[], cookie: Cookie}>;
  requestHeaders: NameValue[];
  includedRequestCookies: IncludedCookieWithReason[];
  deviceBoundSessionUsages?: Protocol.Network.DeviceBoundSessionWithUsage[];
  clientSecurityState?: Protocol.Network.ClientSecurityState;
  connectTiming: Protocol.Network.ConnectTiming;
  siteHasCookieInOtherPartition?: boolean;
  appliedNetworkConditionsId?: string;
}

export interface ExtraResponseInfo {
  blockedResponseCookies:
      Array<{blockedReasons: Protocol.Network.SetCookieBlockedReason[], cookieLine: string, cookie: Cookie|null}>;
  responseHeaders: NameValue[];
  responseHeadersText?: string;
  resourceIPAddressSpace: Protocol.Network.IPAddressSpace;
  statusCode: number|undefined;
  cookiePartitionKey?: Protocol.Network.CookiePartitionKey;
  cookiePartitionKeyOpaque: boolean|undefined;
  exemptedResponseCookies:|
      Array<{cookie: Cookie, cookieLine: string, exemptionReason: Protocol.Network.CookieExemptionReason}>|undefined;
}

export interface EarlyHintsInfo {
  responseHeaders: NameValue[];
}

export type OverrideType = 'content'|'headers';

export enum DirectSocketType {
  TCP = 1,
  UDP_BOUND = 2,
  UDP_CONNECTED = 3,
}

export enum DirectSocketStatus {
  OPENING = 1,
  OPEN = 2,
  CLOSED = 3,
  ABORTED = 4,
}

export interface DirectSocketCreateOptions {
  remoteAddr?: string;
  remotePort?: number;
  localAddr?: string;
  localPort?: number;
  noDelay?: boolean;
  keepAliveDelay?: number;
  sendBufferSize?: number;
  receiveBufferSize?: number;
  dnsQueryType?: Protocol.Network.DirectSocketDnsQueryType;
  multicastLoopback?: boolean;
  multicastTimeToLive?: number;
  multicastAllowAddressSharing?: boolean;
}

export interface DirectSocketOpenInfo {
  remoteAddr?: string;
  remotePort?: number;
  localAddr?: string;
  localPort?: number;
}

export interface DirectSocketInfo {
  type: DirectSocketType;
  status: DirectSocketStatus;
  errorMessage?: string;
  createOptions: DirectSocketCreateOptions;
  openInfo?: DirectSocketOpenInfo;
  joinedMulticastGroups?: Set<string>;
}

export interface DirectSocketChunk {
  data: string;
  type: DirectSocketChunkType;
  timestamp: number;
  // Only for bound udp socket.
  remoteAddress?: string;
  remotePort?: number;
}

export enum DirectSocketChunkType {
  SEND = 'send',
  RECEIVE = 'receive',
}
