From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Niklas Wenzel <dev@nikwen.de>
Date: Wed, 25 Feb 2026 16:24:03 +0100
Subject: feat: allow enabling extensions on custom protocols

This allows us to use Chrome extensions on custom protocols.

The patch can't really be upstreamed, unfortunately, because there are
other URLPattern functions that we don't patch that Chrome needs.

Patching those properly would require replacing the bitmask logic in
URLPattern with a more flexible solution. This would be a larger effort
and Chromium might reject it for performance reasons.

See: https://source.chromium.org/chromium/chromium/src/+/main:extensions/common/url_pattern.h;l=53-74;drc=50dbcddad2f8e36ddfcec21d4551f389df425c37

This patch makes it work in the context of Electron.

diff --git a/extensions/browser/api/content_settings/content_settings_helpers.cc b/extensions/browser/api/content_settings/content_settings_helpers.cc
index 34fa528a82f03891c89b3bb95bc9d2a135ee5f36..f88041554b828215a32dbb4aadcc73df40e6d8c2 100644
--- a/extensions/browser/api/content_settings/content_settings_helpers.cc
+++ b/extensions/browser/api/content_settings/content_settings_helpers.cc
@@ -37,7 +37,7 @@ ContentSettingsPattern ParseExtensionPattern(const std::string& pattern_str,
                                              std::string* error) {
   const int kAllowedSchemes =
       URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS |
-      URLPattern::SCHEME_FILE;
+      URLPattern::SCHEME_FILE | URLPattern::SCHEME_ELECTRON_CUSTOM_PROTOCOLS;
   URLPattern url_pattern(kAllowedSchemes);
   URLPattern::ParseResult result = url_pattern.Parse(pattern_str);
   if (result != URLPattern::ParseResult::kSuccess) {
diff --git a/extensions/browser/api/web_request/extension_web_request_event_router.h b/extensions/browser/api/web_request/extension_web_request_event_router.h
index 3de9285a548c1812783e90e76417b060dea612af..fad75693a26a139695f822e8b7567b0d38bb53cc 100644
--- a/extensions/browser/api/web_request/extension_web_request_event_router.h
+++ b/extensions/browser/api/web_request/extension_web_request_event_router.h
@@ -52,7 +52,8 @@ inline constexpr int kWebRequestFilterValidSchemes =
     URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS |
     URLPattern::SCHEME_FTP | URLPattern::SCHEME_FILE |
     URLPattern::SCHEME_EXTENSION | URLPattern::SCHEME_WS |
-    URLPattern::SCHEME_WSS | URLPattern::SCHEME_UUID_IN_PACKAGE;
+    URLPattern::SCHEME_WSS | URLPattern::SCHEME_UUID_IN_PACKAGE |
+    URLPattern::SCHEME_ELECTRON_CUSTOM_PROTOCOLS;
 
 class WebRequestEventRouter : public KeyedService {
  public:
diff --git a/extensions/common/extension.cc b/extensions/common/extension.cc
index 25f6860b1db1fecba457c06ecff9263efa4a6a8a..bc7e9834d2ad3f7abd8a58ee49f6a7f447915754 100644
--- a/extensions/common/extension.cc
+++ b/extensions/common/extension.cc
@@ -219,7 +219,8 @@ const int Extension::kValidHostPermissionSchemes =
     URLPattern::SCHEME_CHROMEUI | URLPattern::SCHEME_HTTP |
     URLPattern::SCHEME_HTTPS | URLPattern::SCHEME_FILE |
     URLPattern::SCHEME_FTP | URLPattern::SCHEME_WS | URLPattern::SCHEME_WSS |
-    URLPattern::SCHEME_UUID_IN_PACKAGE;
+    URLPattern::SCHEME_UUID_IN_PACKAGE |
+    URLPattern::SCHEME_ELECTRON_CUSTOM_PROTOCOLS;
 
 //
 // Extension
diff --git a/extensions/common/url_pattern.cc b/extensions/common/url_pattern.cc
index d4328ca22fdeefd3dca88bfe959dfb849705b109..ba24e788d4a2e467d24f6369e2d93ea3b4a0c9d7 100644
--- a/extensions/common/url_pattern.cc
+++ b/extensions/common/url_pattern.cc
@@ -140,6 +140,11 @@ bool URLPattern::IsValidSchemeForExtensions(std::string_view scheme) {
       return true;
     }
   }
+  for (auto& extension_scheme : url::GetExtensionSchemes()) {
+    if (scheme == extension_scheme) {
+      return true;
+    }
+  }
   return false;
 }
 
@@ -401,6 +406,14 @@ bool URLPattern::IsValidScheme(std::string_view scheme) const {
     }
   }
 
+  if (valid_schemes_ & URLPattern::SCHEME_ELECTRON_CUSTOM_PROTOCOLS) {
+    for (auto& extension_scheme : url::GetExtensionSchemes()) {
+      if (scheme == extension_scheme) {
+        return true;
+      }
+    }
+  }
+
   return false;
 }
 
diff --git a/extensions/common/url_pattern.h b/extensions/common/url_pattern.h
index 4d09251b0160644d86682ad3db7c41b50f360e6f..8a626e14eff2d58d8218a7b0df820c6c0522b00f 100644
--- a/extensions/common/url_pattern.h
+++ b/extensions/common/url_pattern.h
@@ -64,6 +64,9 @@ class URLPattern {
     SCHEME_DATA = 1 << 9,
     SCHEME_UUID_IN_PACKAGE = 1 << 10,
 
+    // Represents the schemes returned by url::GetExtensionSchemes().
+    SCHEME_ELECTRON_CUSTOM_PROTOCOLS = 1 << 11,
+
     // IMPORTANT!
     // SCHEME_ALL will match every scheme, including chrome://, chrome-
     // extension://, about:, etc. Because this has lots of security
diff --git a/extensions/common/user_script.cc b/extensions/common/user_script.cc
index f680ef4d31d580a285abe51387e3df043d4458f1..afde40d56d7874aa04ea2b1d881e5cab79fd7661 100644
--- a/extensions/common/user_script.cc
+++ b/extensions/common/user_script.cc
@@ -69,7 +69,8 @@ enum {
   kValidUserScriptSchemes = URLPattern::SCHEME_CHROMEUI |
                             URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS |
                             URLPattern::SCHEME_FILE | URLPattern::SCHEME_FTP |
-                            URLPattern::SCHEME_UUID_IN_PACKAGE
+                            URLPattern::SCHEME_UUID_IN_PACKAGE |
+                            URLPattern::SCHEME_ELECTRON_CUSTOM_PROTOCOLS
 };
 
 // static
diff --git a/url/url_util.cc b/url/url_util.cc
index 21cfd314fd52727e4b07de880bd80a0bec7407d5..cb90ea396122d8f5dc27e6d9ffc36cc0abedb97d 100644
--- a/url/url_util.cc
+++ b/url/url_util.cc
@@ -134,6 +134,9 @@ struct SchemeRegistry {
   // Embedder schemes that have V8 code cache enabled in js and wasm scripts.
   std::vector<std::string> code_cache_schemes = {};
 
+  // Embedder schemes on which Chrome extensions can be used.
+  std::vector<std::string> extension_schemes = {};
+
   // Schemes with a predefined default custom handler.
   std::vector<SchemeWithHandler> predefined_handler_schemes;
 
@@ -678,6 +681,15 @@ const std::vector<std::string>& GetCodeCacheSchemes() {
   return GetSchemeRegistry().code_cache_schemes;
 }
 
+void AddExtensionScheme(std::string_view new_scheme) {
+  DoAddScheme(new_scheme,
+              &GetSchemeRegistryWithoutLocking()->extension_schemes);
+}
+
+const std::vector<std::string>& GetExtensionSchemes() {
+  return GetSchemeRegistry().extension_schemes;
+}
+
 void AddPredefinedHandlerScheme(std::string_view new_scheme,
                                 std::string_view handler) {
   DoAddSchemeWithHandler(
diff --git a/url/url_util.h b/url/url_util.h
index f965c1dbd47781748d3091209d140a128ca7192f..b6d42ae86e4cf981ae00e96a0f296ada50c90d29 100644
--- a/url/url_util.h
+++ b/url/url_util.h
@@ -124,6 +124,11 @@ COMPONENT_EXPORT(URL) const std::vector<std::string>& GetEmptyDocumentSchemes();
 COMPONENT_EXPORT(URL) void AddCodeCacheScheme(std::string_view new_scheme);
 COMPONENT_EXPORT(URL) const std::vector<std::string>& GetCodeCacheSchemes();
 
+// Adds an application-defined scheme to the list of schemes on which Chrome
+// extensions can be used.
+COMPONENT_EXPORT(URL) void AddExtensionScheme(std::string_view new_scheme);
+COMPONENT_EXPORT(URL) const std::vector<std::string>& GetExtensionSchemes();
+
 // Adds a scheme with a predefined default handler.
 //
 // This pair of strings must be normalized protocol handler parameters as
