Add issues for CORS disabled scheme problems

This CL adds issues for the CORS error codes

CorsDisabledScheme

Screenshot: https://imgur.com/a/3nVmUn6

Bug: chromium:1141824
Change-Id: I29f085e660ba6985b028cbc52e9460857525da52
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2897284
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: Wolfgang Beyer <wolfi@chromium.org>
diff --git a/config/gni/all_devtools_files.gni b/config/gni/all_devtools_files.gni
index b16b95c..bac60f1 100644
--- a/config/gni/all_devtools_files.gni
+++ b/config/gni/all_devtools_files.gni
@@ -213,6 +213,7 @@
   "front_end/models/issues_manager/descriptions/heavyAd.md",
   "front_end/models/issues_manager/descriptions/corsAllowCredentialsRequired.md",
   "front_end/models/issues_manager/descriptions/corsDisallowedByMode.md",
+  "front_end/models/issues_manager/descriptions/corsDisabledScheme.md",
   "front_end/models/issues_manager/descriptions/corsHeaderDisallowedByPreflightResponse.md",
   "front_end/models/issues_manager/descriptions/corsInvalidHeaderValues.md",
   "front_end/models/issues_manager/descriptions/corsMethodDisallowedByPreflightResponse.md",
diff --git a/config/gni/devtools_grd_files.gni b/config/gni/devtools_grd_files.gni
index c3508ab..5b8f46d 100644
--- a/config/gni/devtools_grd_files.gni
+++ b/config/gni/devtools_grd_files.gni
@@ -246,6 +246,7 @@
   "front_end/models/issues_manager/descriptions/TwaHttpError.md",
   "front_end/models/issues_manager/descriptions/TwaPageUnavailableOffline.md",
   "front_end/models/issues_manager/descriptions/corsAllowCredentialsRequired.md",
+  "front_end/models/issues_manager/descriptions/corsDisabledScheme.md",
   "front_end/models/issues_manager/descriptions/corsDisallowedByMode.md",
   "front_end/models/issues_manager/descriptions/corsHeaderDisallowedByPreflightResponse.md",
   "front_end/models/issues_manager/descriptions/corsInsecurePrivateNetwork.md",
diff --git a/front_end/core/i18n/locales/en-US.json b/front_end/core/i18n/locales/en-US.json
index 530c552..7c2df4c 100644
--- a/front_end/core/i18n/locales/en-US.json
+++ b/front_end/core/i18n/locales/en-US.json
@@ -4982,6 +4982,9 @@
   "panels/issues/CorsIssueDetailsView.ts | status": {
     "message": "Status"
   },
+  "panels/issues/CorsIssueDetailsView.ts | unsupportedScheme": {
+    "message": "Unsupported Scheme"
+  },
   "panels/issues/CorsIssueDetailsView.ts | warning": {
     "message": "warning"
   },
diff --git a/front_end/core/i18n/locales/en-XL.json b/front_end/core/i18n/locales/en-XL.json
index 418efdf..a86bfb1 100644
--- a/front_end/core/i18n/locales/en-XL.json
+++ b/front_end/core/i18n/locales/en-XL.json
@@ -4982,6 +4982,9 @@
   "panels/issues/CorsIssueDetailsView.ts | status": {
     "message": "Ŝt́ât́ûś"
   },
+  "panels/issues/CorsIssueDetailsView.ts | unsupportedScheme": {
+    "message": "Ûńŝúp̂ṕôŕt̂éd̂ Śĉh́êḿê"
+  },
   "panels/issues/CorsIssueDetailsView.ts | warning": {
     "message": "ŵár̂ńîńĝ"
   },
diff --git a/front_end/models/issues_manager/BUILD.gn b/front_end/models/issues_manager/BUILD.gn
index 837c62b..ae3a193 100644
--- a/front_end/models/issues_manager/BUILD.gn
+++ b/front_end/models/issues_manager/BUILD.gn
@@ -45,6 +45,7 @@
   "CoepCorpNotSameSite.md",
   "CoepFrameResourceNeedsCoepHeader.md",
   "corsAllowCredentialsRequired.md",
+  "corsDisabledScheme.md",
   "corsDisallowedByMode.md",
   "corsHeaderDisallowedByPreflightResponse.md",
   "corsInsecurePrivateNetwork.md",
diff --git a/front_end/models/issues_manager/CorsIssue.ts b/front_end/models/issues_manager/CorsIssue.ts
index f8b89d4..d66ccdc 100644
--- a/front_end/models/issues_manager/CorsIssue.ts
+++ b/front_end/models/issues_manager/CorsIssue.ts
@@ -198,6 +198,13 @@
           }],
         };
       case IssueCode.CorsDisabledScheme:
+        return {
+          file: 'corsDisabledScheme.md',
+          links: [{
+            link: 'https://web.dev/cross-origin-resource-sharing',
+            linkTitle: i18nString(UIStrings.CORS),
+          }],
+        };
       case IssueCode.PreflightMissingAllowExternal:
       case IssueCode.PreflightInvalidAllowExternal:
       case IssueCode.InvalidResponse:
diff --git a/front_end/models/issues_manager/descriptions/corsDisabledScheme.md b/front_end/models/issues_manager/descriptions/corsDisabledScheme.md
new file mode 100644
index 0000000..c8d18ef
--- /dev/null
+++ b/front_end/models/issues_manager/descriptions/corsDisabledScheme.md
@@ -0,0 +1,7 @@
+# Ensure CORS requests are made on supported schemes
+
+A cross-origin resource sharing (CORS) request was blocked because the scheme of the request's URL doesn't support CORS.
+
+To fix this issue, ensure all CORS request URLs specify a supported scheme, e.g. most commonly https://.
+
+Note that if an opaque response is sufficient, then for some schemes the request's mode can be set to `no-cors` to fetch the resource with CORS disabled; that way the scheme doesn't need to support CORS, but the response content is inaccessible (opaque).
diff --git a/front_end/panels/issues/CorsIssueDetailsView.ts b/front_end/panels/issues/CorsIssueDetailsView.ts
index de42db4..af90333 100644
--- a/front_end/panels/issues/CorsIssueDetailsView.ts
+++ b/front_end/panels/issues/CorsIssueDetailsView.ts
@@ -112,6 +112,10 @@
   *@description Header for the source location column
   */
   sourceLocation: 'Source Location',
+  /**
+  *@description Header for the column with the URL scheme that is not supported by fetch
+  */
+  unsupportedScheme: 'Unsupported Scheme',
 };
 const str_ = i18n.i18n.registerUIStrings('panels/issues/CorsIssueDetailsView.ts', UIStrings);
 const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -190,9 +194,13 @@
         this.appendColumnTitle(header, i18nString(UIStrings.initiatorContext));
         this.appendColumnTitle(header, i18nString(UIStrings.sourceLocation));
         break;
+      case IssuesManager.CorsIssue.IssueCode.CorsDisabledScheme:
+        this.appendColumnTitle(header, i18nString(UIStrings.initiatorContext));
+        this.appendColumnTitle(header, i18nString(UIStrings.sourceLocation));
+        this.appendColumnTitle(header, i18nString(UIStrings.unsupportedScheme));
+        break;
       default:
         Platform.assertUnhandled<IssuesManager.CorsIssue.IssueCode.NoCorsRedirectModeNotFollow|
-                                 IssuesManager.CorsIssue.IssueCode.CorsDisabledScheme|
                                  IssuesManager.CorsIssue.IssueCode.PreflightMissingAllowExternal|
                                  IssuesManager.CorsIssue.IssueCode.PreflightInvalidAllowExternal|
                                  IssuesManager.CorsIssue.IssueCode.InvalidResponse>(issueCode);
@@ -383,11 +391,22 @@
         this.appendIssueDetailCell(element, details.initiatorOrigin ?? '', 'code-example');
         this.appendSourceLocation(element, details.location, issue.model()?.getTargetIfNotDisposed());
         break;
+      case IssuesManager.CorsIssue.IssueCode.CorsDisabledScheme:
+        element.appendChild(this.createRequestCell(details.request, {
+          highlightHeader: {
+            section: Network.NetworkSearchScope.UIHeaderSection.Response,
+            name: CorsIssueDetailsView.getHeaderFromError(corsError),
+          },
+        }));
+        this.appendStatus(element, details.isWarning);
+        this.appendIssueDetailCell(element, details.initiatorOrigin ?? '', 'code-example');
+        this.appendSourceLocation(element, details.location, issue.model()?.getTargetIfNotDisposed());
+        this.appendIssueDetailCell(element, details.corsErrorStatus.failedParameter ?? '', 'code-example');
+        break;
       default:
         element.appendChild(this.createRequestCell(details.request));
         this.appendStatus(element, details.isWarning);
         Platform.assertUnhandled<IssuesManager.CorsIssue.IssueCode.NoCorsRedirectModeNotFollow|
-                                 IssuesManager.CorsIssue.IssueCode.CorsDisabledScheme|
                                  IssuesManager.CorsIssue.IssueCode.PreflightMissingAllowExternal|
                                  IssuesManager.CorsIssue.IssueCode.PreflightInvalidAllowExternal|
                                  IssuesManager.CorsIssue.IssueCode.InvalidResponse>(issueCode);
diff --git a/test/e2e/issues/cors-issues_test.ts b/test/e2e/issues/cors-issues_test.ts
index 8ebcf39..72c3cf8 100644
--- a/test/e2e/issues/cors-issues_test.ts
+++ b/test/e2e/issues/cors-issues_test.ts
@@ -409,4 +409,43 @@
       /.*:\d+/,
     ]);
   });
+
+  it('should display CORS issues that are unsupported by the scheme', async () => {
+    await goToResource('empty.html');
+    const {target} = getBrowserAndPages();
+    await target.evaluate(async () => {
+      try {
+        const url = new URL('/', document.location.toString())
+                        .toString()
+                        .replace('https://localhost', 'webdav://devtools.oopif.test');
+        await fetch(url);
+      } catch (e) {
+      }
+    });
+    await navigateToIssuesTab();
+    await expandIssue();
+    const issueElement = await getIssueByTitle('Ensure CORS requests are made on supported schemes');
+    assertNotNull(issueElement);
+    const section = await getResourcesElement('request', issueElement, '.cors-issue-affected-resource-label');
+    const text = await section.label.evaluate(el => el.textContent);
+    assert.strictEqual(text, '1 request');
+    await ensureResourceSectionIsExpanded(section);
+    const table = await extractTableFromResourceSection(section.content);
+    assertNotNull(table);
+    assert.strictEqual(table.length, 2);
+    assert.deepEqual(table[0], [
+      'Request',
+      'Status',
+      'Initiator Context',
+      'Source Location',
+      'Unsupported Scheme',
+    ]);
+    assertMatchArray(table[1], [
+      /^devtools.oopif.test.*\//,
+      'blocked',
+      /^https:\/\/localhost.*/,
+      /.*:\d+/,
+      'webdav',
+    ]);
+  });
 });