
// Copyright 2020 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 Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import type * as Platform from '../../core/platform/platform.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';

import {Issue, IssueCategory, IssueKind} from './Issue.js';
import {
  type LazyMarkdownIssueDescription,
  type MarkdownIssueDescription,
  resolveLazyDescription,
} from './MarkdownIssueDescription.js';

const UIStrings = {
  /**
   * @description Label for the link for SameSiteCookies Issues
   */
  samesiteCookiesExplained: 'SameSite cookies explained',
  /**
   * @description Label for the link for Schemeful Same-Site Issues
   */
  howSchemefulSamesiteWorks: 'How Schemeful Same-Site Works',
  /**
   * @description Label for a link for cross-site redirect Issues.
   */
  fileCrosSiteRedirectBug: 'File a bug',
  /**
   * @description text to show in Console panel when a third-party cookie is blocked in Chrome.
   */
  consoleTpcdErrorMessage:
      'Third-party cookie is blocked in Chrome either because of Chrome flags or browser configuration.',

} as const;
const str_ = i18n.i18n.registerUIStrings('models/issues_manager/CookieIssue.ts', UIStrings);
const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_);

/** The enum string values need to match the IssueExpanded enum values in UserMetrics.ts. **/
export const enum CookieIssueSubCategory {
  GENERIC_COOKIE = 'GenericCookie',
  SAME_SITE_COOKIE = 'SameSiteCookie',
  THIRD_PARTY_PHASEOUT_COOKIE = 'ThirdPartyPhaseoutCookie',
}

/** Enum to show cookie status from the security panel's third-party cookie report tool **/
export const enum CookieStatus {
  BLOCKED = 0,
  ALLOWED = 1,
  ALLOWED_BY_GRACE_PERIOD = 2,
  ALLOWED_BY_HEURISTICS = 3,
}

export class CookieIssue extends Issue<Protocol.Audits.CookieIssueDetails> {
  cookieId(): string {
    const details = this.details();
    if (details.cookie) {
      const {domain, path, name} = details.cookie;
      const cookieId = `${domain};${path};${name}`;
      return cookieId;
    }
    return this.details().rawCookieLine ?? 'no-cookie-info';
  }

  primaryKey(): string {
    const details = this.details();
    const requestId = details.request ? details.request.requestId : 'no-request';
    return `${this.code()}-(${this.cookieId()})-(${requestId})`;
  }

  /**
   * Returns an array of issues from a given CookieIssueDetails.
   */
  static createIssuesFromCookieIssueDetails(
      cookieIssueDetails: Protocol.Audits.CookieIssueDetails, issuesModel: SDK.IssuesModel.IssuesModel|null,
      issueId: Protocol.Audits.IssueId|undefined): CookieIssue[] {
    const issues: CookieIssue[] = [];

    // Exclusion reasons have priority. It means a cookie was blocked. Create an issue
    // for every exclusion reason but ignore warning reasons if the cookie was blocked.
    // Some exclusion reasons are dependent on warning reasons existing in order to produce an issue.
    if (cookieIssueDetails.cookieExclusionReasons && cookieIssueDetails.cookieExclusionReasons.length > 0) {
      for (const exclusionReason of cookieIssueDetails.cookieExclusionReasons) {
        const code = CookieIssue.codeForCookieIssueDetails(
            exclusionReason, cookieIssueDetails.cookieWarningReasons, cookieIssueDetails.operation,
            cookieIssueDetails.cookieUrl as Platform.DevToolsPath.UrlString | undefined);
        if (code) {
          issues.push(new CookieIssue(code, cookieIssueDetails, issuesModel, issueId));
        }
      }
      return issues;
    }

    if (cookieIssueDetails.cookieWarningReasons) {
      for (const warningReason of cookieIssueDetails.cookieWarningReasons) {
        // warningReasons should be an empty array here.
        const code = CookieIssue.codeForCookieIssueDetails(
            warningReason, [], cookieIssueDetails.operation,
            cookieIssueDetails.cookieUrl as Platform.DevToolsPath.UrlString | undefined);
        if (code) {
          issues.push(new CookieIssue(code, cookieIssueDetails, issuesModel, issueId));
        }
      }
    }
    return issues;
  }

  /**
   * Calculates an issue code from a reason, an operation, and an array of warningReasons. All these together
   * can uniquely identify a specific cookie issue.
   * warningReasons is only needed for some CookieExclusionReason in order to determine if an issue should be raised.
   * It is not required if reason is a CookieWarningReason.
   *
   * The issue code will be mapped to a CookieIssueSubCategory enum for metric purpose.
   */
  static codeForCookieIssueDetails(
      reason: Protocol.Audits.CookieExclusionReason|Protocol.Audits.CookieWarningReason,
      warningReasons: Protocol.Audits.CookieWarningReason[], operation: Protocol.Audits.CookieOperation,
      cookieUrl?: Platform.DevToolsPath.UrlString): string|null {
    const isURLSecure =
        cookieUrl && (Common.ParsedURL.schemeIs(cookieUrl, 'https:') || Common.ParsedURL.schemeIs(cookieUrl, 'wss:'));
    const secure = isURLSecure ? 'Secure' : 'Insecure';

    if (reason === Protocol.Audits.CookieExclusionReason.ExcludeSameSiteStrict ||
        reason === Protocol.Audits.CookieExclusionReason.ExcludeSameSiteLax ||
        reason === Protocol.Audits.CookieExclusionReason.ExcludeSameSiteUnspecifiedTreatedAsLax) {
      if (warningReasons && warningReasons.length > 0) {
        if (warningReasons.includes(Protocol.Audits.CookieWarningReason.WarnSameSiteStrictLaxDowngradeStrict)) {
          return [
            Protocol.Audits.InspectorIssueCode.CookieIssue,
            'ExcludeNavigationContextDowngrade',
            secure,
          ].join('::');
        }

        if (warningReasons.includes(Protocol.Audits.CookieWarningReason.WarnSameSiteStrictCrossDowngradeStrict) ||
            warningReasons.includes(Protocol.Audits.CookieWarningReason.WarnSameSiteStrictCrossDowngradeLax) ||
            warningReasons.includes(Protocol.Audits.CookieWarningReason.WarnSameSiteLaxCrossDowngradeStrict) ||
            warningReasons.includes(Protocol.Audits.CookieWarningReason.WarnSameSiteLaxCrossDowngradeLax)) {
          return [
            Protocol.Audits.InspectorIssueCode.CookieIssue,
            'ExcludeContextDowngrade',
            operation,
            secure,
          ].join('::');
        }
      }

      if (warningReasons.includes(Protocol.Audits.CookieWarningReason.WarnCrossSiteRedirectDowngradeChangesInclusion)) {
        return [
          Protocol.Audits.InspectorIssueCode.CookieIssue,
          'CrossSiteRedirectDowngradeChangesInclusion',
        ].join('::');
      }

      // If we have ExcludeSameSiteUnspecifiedTreatedAsLax but no corresponding warnings, then add just
      // the Issue code for ExcludeSameSiteUnspecifiedTreatedAsLax.
      if (reason === Protocol.Audits.CookieExclusionReason.ExcludeSameSiteUnspecifiedTreatedAsLax) {
        return [Protocol.Audits.InspectorIssueCode.CookieIssue, reason, operation].join('::');
      }

      // ExcludeSameSiteStrict and ExcludeSameSiteLax require being paired with an appropriate warning. We didn't
      // find one of those warnings so return null to indicate there shouldn't be an issue created.
      return null;
    }

    if (reason === Protocol.Audits.CookieWarningReason.WarnSameSiteStrictLaxDowngradeStrict) {
      return [Protocol.Audits.InspectorIssueCode.CookieIssue, reason, secure].join('::');
    }
    // These have the same message.
    if (reason === Protocol.Audits.CookieWarningReason.WarnSameSiteStrictCrossDowngradeStrict ||
        reason === Protocol.Audits.CookieWarningReason.WarnSameSiteStrictCrossDowngradeLax ||
        reason === Protocol.Audits.CookieWarningReason.WarnSameSiteLaxCrossDowngradeLax ||
        reason === Protocol.Audits.CookieWarningReason.WarnSameSiteLaxCrossDowngradeStrict) {
      return [Protocol.Audits.InspectorIssueCode.CookieIssue, 'WarnCrossDowngrade', operation, secure].join('::');
    }

    if (reason === Protocol.Audits.CookieExclusionReason.ExcludePortMismatch) {
      return [Protocol.Audits.InspectorIssueCode.CookieIssue, 'ExcludePortMismatch'].join('::');
    }

    if (reason === Protocol.Audits.CookieExclusionReason.ExcludeSchemeMismatch) {
      return [Protocol.Audits.InspectorIssueCode.CookieIssue, 'ExcludeSchemeMismatch'].join('::');
    }
    return [Protocol.Audits.InspectorIssueCode.CookieIssue, reason, operation].join('::');
  }

  override cookies(): Iterable<Protocol.Audits.AffectedCookie> {
    const details = this.details();
    if (details.cookie) {
      return [details.cookie];
    }
    return [];
  }

  override rawCookieLines(): Iterable<string> {
    const details = this.details();
    if (details.rawCookieLine) {
      return [details.rawCookieLine];
    }
    return [];
  }

  override requests(): Iterable<Protocol.Audits.AffectedRequest> {
    const details = this.details();
    if (details.request) {
      return [details.request];
    }
    return [];
  }

  getCategory(): IssueCategory {
    return IssueCategory.COOKIE;
  }

  getDescription(): MarkdownIssueDescription|null {
    const description = issueDescriptions.get(this.code());
    if (!description) {
      return null;
    }
    return resolveLazyDescription(description);
  }

  override isCausedByThirdParty(): boolean {
    const outermostFrame = SDK.FrameManager.FrameManager.instance().getOutermostFrame();
    return isCausedByThirdParty(outermostFrame, this.details().cookieUrl, this.details().siteForCookies);
  }

  getKind(): IssueKind {
    if (this.details().cookieExclusionReasons?.length > 0) {
      return IssueKind.PAGE_ERROR;
    }
    return IssueKind.BREAKING_CHANGE;
  }

  static getCookieStatus(cookieIssueDetails: Protocol.Audits.CookieIssueDetails): CookieStatus|undefined {
    if (cookieIssueDetails.cookieExclusionReasons.includes(
            Protocol.Audits.CookieExclusionReason.ExcludeThirdPartyPhaseout)) {
      return CookieStatus.BLOCKED;
    }

    if (cookieIssueDetails.cookieWarningReasons.includes(
            Protocol.Audits.CookieWarningReason.WarnDeprecationTrialMetadata)) {
      return CookieStatus.ALLOWED_BY_GRACE_PERIOD;
    }

    if (cookieIssueDetails.cookieWarningReasons.includes(
            Protocol.Audits.CookieWarningReason.WarnThirdPartyCookieHeuristic)) {
      return CookieStatus.ALLOWED_BY_HEURISTICS;
    }

    if (cookieIssueDetails.cookieWarningReasons.includes(Protocol.Audits.CookieWarningReason.WarnThirdPartyPhaseout)) {
      return CookieStatus.ALLOWED;
    }

    return;
  }

  static fromInspectorIssue(
      issuesModel: SDK.IssuesModel.IssuesModel|null, inspectorIssue: Protocol.Audits.InspectorIssue): CookieIssue[] {
    const cookieIssueDetails = inspectorIssue.details.cookieIssueDetails;
    if (!cookieIssueDetails) {
      console.warn('Cookie issue without details received.');
      return [];
    }

    return CookieIssue.createIssuesFromCookieIssueDetails(cookieIssueDetails, issuesModel, inspectorIssue.issueId);
  }

  static getSubCategory(code: string): CookieIssueSubCategory {
    if (code.includes('SameSite') || code.includes('Downgrade')) {
      return CookieIssueSubCategory.SAME_SITE_COOKIE;
    }
    if (code.includes('ThirdPartyPhaseout')) {
      return CookieIssueSubCategory.THIRD_PARTY_PHASEOUT_COOKIE;
    }
    return CookieIssueSubCategory.GENERIC_COOKIE;
  }

  static isThirdPartyCookiePhaseoutRelatedIssue(issue: Issue): boolean {
    const excludeFromAggregate = [
      Protocol.Audits.CookieWarningReason.WarnThirdPartyCookieHeuristic,
      Protocol.Audits.CookieWarningReason.WarnDeprecationTrialMetadata,
      Protocol.Audits.CookieWarningReason.WarnThirdPartyPhaseout,
      Protocol.Audits.CookieExclusionReason.ExcludeThirdPartyPhaseout,
    ];

    return (excludeFromAggregate.some(exclude => issue.code().includes(exclude)));
  }

  override maybeCreateConsoleMessage(): SDK.ConsoleModel.ConsoleMessage|undefined {
    const issuesModel = this.model();
    if (issuesModel && this.code().includes(Protocol.Audits.CookieExclusionReason.ExcludeThirdPartyPhaseout)) {
      return new SDK.ConsoleModel.ConsoleMessage(
          issuesModel.target().model(SDK.RuntimeModel.RuntimeModel), Common.Console.FrontendMessageSource.ISSUE_PANEL,
          Protocol.Log.LogEntryLevel.Warning, UIStrings.consoleTpcdErrorMessage, {
            url: this.details().request?.url as Platform.DevToolsPath.UrlString | undefined,
            affectedResources: {requestId: this.details().request?.requestId, issueId: this.issueId},
          });
    }
    return;
  }
}

/**
 * Exported for unit test.
 */
export function isCausedByThirdParty(
    outermostFrame: SDK.ResourceTreeModel.ResourceTreeFrame|null, cookieUrl?: string,
    siteForCookies?: string): boolean {
  if (!outermostFrame) {
    // The outermost frame is not yet available. Consider this issue as a third-party issue
    // until the outermost frame is available. This will prevent the issue from being visible
    // for only just a split second.
    return true;
  }
  // The value that should be consulted for the third-partiness as defined in
  // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-same-site#section-2.1.1
  if (!siteForCookies) {
    return true;
  }

  // In the case of no domain and registry, we assume its an IP address or localhost
  // during development, in this case we classify the issue as first-party.
  if (!cookieUrl || outermostFrame.domainAndRegistry() === '') {
    return false;
  }

  const parsedCookieUrl = Common.ParsedURL.ParsedURL.fromString(cookieUrl);
  if (!parsedCookieUrl) {
    return false;
  }

  // For both operation types we compare the cookieUrl's domain  with the outermost frames
  // registered domain to determine first-party vs third-party. If they don't match
  // then we consider this issue a third-party issue.
  //
  // For a Set operation: The Set-Cookie response is part of a request to a third-party.
  //
  // For a Read operation: The cookie was included in a request to a third-party
  //     site. Only cookies that have their domain also set to this third-party
  //     are included in the request. We assume that the cookie was set by the same
  //     third-party at some point, so we treat this as a third-party issue.
  //
  // TODO(crbug.com/1080589): Use "First-Party sets" instead of the sites registered domain.
  return !isSubdomainOf(parsedCookieUrl.domain(), outermostFrame.domainAndRegistry());
}

function isSubdomainOf(subdomain: string, superdomain: string): boolean {
  // Subdomain must be identical or have strictly more labels than the
  // superdomain.
  if (subdomain.length <= superdomain.length) {
    return subdomain === superdomain;
  }

  // Superdomain must be suffix of subdomain, and the last character not
  // included in the matching substring must be a dot.
  if (!subdomain.endsWith(superdomain)) {
    return false;
  }

  const subdomainWithoutSuperdomian = subdomain.substr(0, subdomain.length - superdomain.length);
  return subdomainWithoutSuperdomian.endsWith('.');
}

const sameSiteUnspecifiedWarnRead: LazyMarkdownIssueDescription = {
  file: 'SameSiteUnspecifiedLaxAllowUnsafeRead.md',
  links: [
    {
      link: 'https://web.dev/samesite-cookies-explained/',
      linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained),
    },
  ],
};

const sameSiteUnspecifiedWarnSet: LazyMarkdownIssueDescription = {
  file: 'SameSiteUnspecifiedLaxAllowUnsafeSet.md',
  links: [
    {
      link: 'https://web.dev/samesite-cookies-explained/',
      linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained),
    },
  ],
};

const sameSiteNoneInsecureErrorRead: LazyMarkdownIssueDescription = {
  file: 'SameSiteNoneInsecureErrorRead.md',
  links: [
    {
      link: 'https://web.dev/samesite-cookies-explained/',
      linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained),
    },
  ],
};

const sameSiteNoneInsecureErrorSet: LazyMarkdownIssueDescription = {
  file: 'SameSiteNoneInsecureErrorSet.md',
  links: [
    {
      link: 'https://web.dev/samesite-cookies-explained/',
      linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained),
    },
  ],
};

const sameSiteNoneInsecureWarnRead: LazyMarkdownIssueDescription = {
  file: 'SameSiteNoneInsecureWarnRead.md',
  links: [
    {
      link: 'https://web.dev/samesite-cookies-explained/',
      linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained),
    },
  ],
};

const sameSiteNoneInsecureWarnSet: LazyMarkdownIssueDescription = {
  file: 'SameSiteNoneInsecureWarnSet.md',
  links: [
    {
      link: 'https://web.dev/samesite-cookies-explained/',
      linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained),
    },
  ],
};

const schemefulSameSiteArticles =
    [{link: 'https://web.dev/schemeful-samesite/', linkTitle: i18nLazyString(UIStrings.howSchemefulSamesiteWorks)}];

function schemefulSameSiteSubstitutions(
    {isDestinationSecure, isOriginSecure}: {isDestinationSecure: boolean, isOriginSecure: boolean}):
    Map<string, () => string> {
  return new Map([
    // TODO(crbug.com/1168438): Use translated phrases once the issue description is localized.
    ['PLACEHOLDER_destination', () => isDestinationSecure ? 'a secure' : 'an insecure'],
    ['PLACEHOLDER_origin', () => isOriginSecure ? 'a secure' : 'an insecure'],
  ]);
}

function sameSiteWarnStrictLaxDowngradeStrict(isSecure: boolean): LazyMarkdownIssueDescription {
  return {
    file: 'SameSiteWarnStrictLaxDowngradeStrict.md',
    substitutions: schemefulSameSiteSubstitutions({isDestinationSecure: isSecure, isOriginSecure: !isSecure}),
    links: schemefulSameSiteArticles,
  };
}

function sameSiteExcludeNavigationContextDowngrade(isSecure: boolean): LazyMarkdownIssueDescription {
  return {
    file: 'SameSiteExcludeNavigationContextDowngrade.md',
    substitutions: schemefulSameSiteSubstitutions({isDestinationSecure: isSecure, isOriginSecure: !isSecure}),
    links: schemefulSameSiteArticles,
  };
}

function sameSiteWarnCrossDowngradeRead(isSecure: boolean): LazyMarkdownIssueDescription {
  return {
    file: 'SameSiteWarnCrossDowngradeRead.md',
    substitutions: schemefulSameSiteSubstitutions({isDestinationSecure: isSecure, isOriginSecure: !isSecure}),
    links: schemefulSameSiteArticles,
  };
}

function sameSiteExcludeContextDowngradeRead(isSecure: boolean): LazyMarkdownIssueDescription {
  return {
    file: 'SameSiteExcludeContextDowngradeRead.md',
    substitutions: schemefulSameSiteSubstitutions({isDestinationSecure: isSecure, isOriginSecure: !isSecure}),
    links: schemefulSameSiteArticles,
  };
}

function sameSiteWarnCrossDowngradeSet(isSecure: boolean): LazyMarkdownIssueDescription {
  return {
    file: 'SameSiteWarnCrossDowngradeSet.md',
    substitutions: schemefulSameSiteSubstitutions({isDestinationSecure: !isSecure, isOriginSecure: isSecure}),
    links: schemefulSameSiteArticles,
  };
}

function sameSiteExcludeContextDowngradeSet(isSecure: boolean): LazyMarkdownIssueDescription {
  return {
    file: 'SameSiteExcludeContextDowngradeSet.md',
    substitutions: schemefulSameSiteSubstitutions({isDestinationSecure: isSecure, isOriginSecure: !isSecure}),
    links: schemefulSameSiteArticles,
  };
}

const attributeValueExceedsMaxSize: LazyMarkdownIssueDescription = {
  file: 'CookieAttributeValueExceedsMaxSize.md',
  links: [],
};

const warnDomainNonAscii: LazyMarkdownIssueDescription = {
  file: 'cookieWarnDomainNonAscii.md',
  links: [],
};

const excludeDomainNonAscii: LazyMarkdownIssueDescription = {
  file: 'cookieExcludeDomainNonAscii.md',
  links: [],
};

const excludeBlockedWithinRelatedWebsiteSet: LazyMarkdownIssueDescription = {
  file: 'cookieExcludeBlockedWithinRelatedWebsiteSet.md',
  links: [],
};

const cookieCrossSiteRedirectDowngrade: LazyMarkdownIssueDescription = {
  file: 'cookieCrossSiteRedirectDowngrade.md',
  links: [{
    link:
        'https://bugs.chromium.org/p/chromium/issues/entry?template=Defect%20report%20from%20user&summary=[Cross-Site Redirect Chain] <INSERT BUG SUMMARY HERE>&comment=Chrome Version: (copy from chrome://version)%0AChannel: (e.g. Canary, Dev, Beta, Stable)%0A%0AAffected URLs:%0A%0AWhat is the expected result?%0A%0AWhat happens instead?%0A%0AWhat is the purpose of the cross-site redirect?:%0A%0AWhat steps will reproduce the problem?:%0A(1)%0A(2)%0A(3)%0A%0APlease provide any additional information below.&components=Internals%3ENetwork%3ECookies',
    linkTitle: i18nLazyString(UIStrings.fileCrosSiteRedirectBug),
  }],
};

const ExcludePortMismatch: LazyMarkdownIssueDescription = {
  file: 'cookieExcludePortMismatch.md',
  links: [],
};

const ExcludeSchemeMismatch: LazyMarkdownIssueDescription = {
  file: 'cookieExcludeSchemeMismatch.md',
  links: [],
};

// This description will be used by cookie issues that need to be added to the
// issueManager, but aren't intended to be surfaced in the issues pane. This
// is why they are using a placeholder description
const placeholderDescriptionForInvisibleIssues: LazyMarkdownIssueDescription = {
  file: 'placeholderDescriptionForInvisibleIssues.md',
  links: [],
};

const issueDescriptions = new Map<string, LazyMarkdownIssueDescription>([
  // These two don't have a deprecation date yet, but they need to be fixed eventually.
  ['CookieIssue::WarnSameSiteUnspecifiedLaxAllowUnsafe::ReadCookie', sameSiteUnspecifiedWarnRead],
  ['CookieIssue::WarnSameSiteUnspecifiedLaxAllowUnsafe::SetCookie', sameSiteUnspecifiedWarnSet],
  ['CookieIssue::WarnSameSiteUnspecifiedCrossSiteContext::ReadCookie', sameSiteUnspecifiedWarnRead],
  ['CookieIssue::WarnSameSiteUnspecifiedCrossSiteContext::SetCookie', sameSiteUnspecifiedWarnSet],
  ['CookieIssue::ExcludeSameSiteNoneInsecure::ReadCookie', sameSiteNoneInsecureErrorRead],
  ['CookieIssue::ExcludeSameSiteNoneInsecure::SetCookie', sameSiteNoneInsecureErrorSet],
  ['CookieIssue::WarnSameSiteNoneInsecure::ReadCookie', sameSiteNoneInsecureWarnRead],
  ['CookieIssue::WarnSameSiteNoneInsecure::SetCookie', sameSiteNoneInsecureWarnSet],
  ['CookieIssue::WarnSameSiteStrictLaxDowngradeStrict::Secure', sameSiteWarnStrictLaxDowngradeStrict(true)],
  ['CookieIssue::WarnSameSiteStrictLaxDowngradeStrict::Insecure', sameSiteWarnStrictLaxDowngradeStrict(false)],
  ['CookieIssue::WarnCrossDowngrade::ReadCookie::Secure', sameSiteWarnCrossDowngradeRead(true)],
  ['CookieIssue::WarnCrossDowngrade::ReadCookie::Insecure', sameSiteWarnCrossDowngradeRead(false)],
  ['CookieIssue::WarnCrossDowngrade::SetCookie::Secure', sameSiteWarnCrossDowngradeSet(true)],
  ['CookieIssue::WarnCrossDowngrade::SetCookie::Insecure', sameSiteWarnCrossDowngradeSet(false)],
  ['CookieIssue::ExcludeNavigationContextDowngrade::Secure', sameSiteExcludeNavigationContextDowngrade(true)],
  [
    'CookieIssue::ExcludeNavigationContextDowngrade::Insecure',
    sameSiteExcludeNavigationContextDowngrade(false),
  ],
  ['CookieIssue::ExcludeContextDowngrade::ReadCookie::Secure', sameSiteExcludeContextDowngradeRead(true)],
  ['CookieIssue::ExcludeContextDowngrade::ReadCookie::Insecure', sameSiteExcludeContextDowngradeRead(false)],
  ['CookieIssue::ExcludeContextDowngrade::SetCookie::Secure', sameSiteExcludeContextDowngradeSet(true)],
  ['CookieIssue::ExcludeContextDowngrade::SetCookie::Insecure', sameSiteExcludeContextDowngradeSet(false)],
  ['CookieIssue::WarnAttributeValueExceedsMaxSize::ReadCookie', attributeValueExceedsMaxSize],
  ['CookieIssue::WarnAttributeValueExceedsMaxSize::SetCookie', attributeValueExceedsMaxSize],
  ['CookieIssue::WarnDomainNonASCII::ReadCookie', warnDomainNonAscii],
  ['CookieIssue::WarnDomainNonASCII::SetCookie', warnDomainNonAscii],
  ['CookieIssue::ExcludeDomainNonASCII::ReadCookie', excludeDomainNonAscii],
  ['CookieIssue::ExcludeDomainNonASCII::SetCookie', excludeDomainNonAscii],
  [
    'CookieIssue::ExcludeThirdPartyCookieBlockedInRelatedWebsiteSet::ReadCookie',
    excludeBlockedWithinRelatedWebsiteSet,
  ],
  [
    'CookieIssue::ExcludeThirdPartyCookieBlockedInRelatedWebsiteSet::SetCookie',
    excludeBlockedWithinRelatedWebsiteSet,
  ],
  ['CookieIssue::WarnThirdPartyPhaseout::ReadCookie', placeholderDescriptionForInvisibleIssues],
  ['CookieIssue::WarnThirdPartyPhaseout::SetCookie', placeholderDescriptionForInvisibleIssues],
  ['CookieIssue::WarnDeprecationTrialMetadata::ReadCookie', placeholderDescriptionForInvisibleIssues],
  ['CookieIssue::WarnDeprecationTrialMetadata::SetCookie', placeholderDescriptionForInvisibleIssues],
  ['CookieIssue::WarnThirdPartyCookieHeuristic::ReadCookie', placeholderDescriptionForInvisibleIssues],
  ['CookieIssue::WarnThirdPartyCookieHeuristic::SetCookie', placeholderDescriptionForInvisibleIssues],
  ['CookieIssue::ExcludeThirdPartyPhaseout::ReadCookie', placeholderDescriptionForInvisibleIssues],
  ['CookieIssue::ExcludeThirdPartyPhaseout::SetCookie', placeholderDescriptionForInvisibleIssues],
  ['CookieIssue::CrossSiteRedirectDowngradeChangesInclusion', cookieCrossSiteRedirectDowngrade],
  ['CookieIssue::ExcludePortMismatch', ExcludePortMismatch],
  ['CookieIssue::ExcludeSchemeMismatch', ExcludeSchemeMismatch],
]);
