diff --git chrome/browser/extensions/api/streams_private/streams_private_api.cc chrome/browser/extensions/api/streams_private/streams_private_api.cc
index 2f5f7a7645103..9ab6fc1d4a654 100644
--- chrome/browser/extensions/api/streams_private/streams_private_api.cc
+++ chrome/browser/extensions/api/streams_private/streams_private_api.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "cef/libcef/features/runtime.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h"
 #include "components/no_state_prefetch/browser/no_state_prefetch_contents.h"
@@ -18,6 +19,10 @@
 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
 #include "extensions/common/manifest_handlers/mime_types_handler.h"
 
+#if BUILDFLAG(ENABLE_CEF)
+#include "cef/libcef/browser/extensions/alloy_extensions_util.h"
+#endif
+
 namespace extensions {
 
 void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent(
@@ -34,6 +39,7 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent(
   if (!web_contents)
     return;
 
+  if (!cef::IsAlloyRuntimeEnabled()) {
   // If the request was for NoStatePrefetch, abort the prefetcher and do not
   // continue. This is because plugins cancel NoStatePrefetch, see
   // http://crbug.com/343590.
@@ -44,6 +50,7 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent(
     no_state_prefetch_contents->Destroy(prerender::FINAL_STATUS_DOWNLOAD);
     return;
   }
+  }
 
   auto* browser_context = web_contents->GetBrowserContext();
 
@@ -70,9 +77,18 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent(
   // forms of zooming won't work).
   // TODO(1042323): Present a coherent representation of a tab id for portal
   // contents.
-  int tab_id = web_contents->GetOuterWebContents()
-                   ? SessionID::InvalidValue().id()
-                   : ExtensionTabUtil::GetTabId(web_contents);
+  int tab_id;
+  if (web_contents->GetOuterWebContents()) {
+    tab_id = SessionID::InvalidValue().id();
+  } else
+#if BUILDFLAG(ENABLE_CEF)
+  if (cef::IsAlloyRuntimeEnabled()) {
+    tab_id = alloy::GetTabIdForWebContents(web_contents);
+  } else
+#endif  // BUILDFLAG(ENABLE_CEF)
+  {
+    tab_id = ExtensionTabUtil::GetTabId(web_contents);
+  }
 
   std::unique_ptr<StreamContainer> stream_container(
       new StreamContainer(tab_id, embedded, handler_url, extension_id,
diff --git extensions/browser/extension_host.cc extensions/browser/extension_host.cc
index 70279e6b632ad..7d6b662e51957 100644
--- extensions/browser/extension_host.cc
+++ extensions/browser/extension_host.cc
@@ -58,11 +58,12 @@ ExtensionHost::ExtensionHost(const Extension* extension,
   DCHECK(host_type == mojom::ViewType::kExtensionBackgroundPage ||
          host_type == mojom::ViewType::kExtensionDialog ||
          host_type == mojom::ViewType::kExtensionPopup);
-  host_contents_ = WebContents::Create(
+  host_contents_owned_ = WebContents::Create(
       WebContents::CreateParams(browser_context_, site_instance)),
-  content::WebContentsObserver::Observe(host_contents_.get());
+  host_contents_ = host_contents_owned_.get();
+  content::WebContentsObserver::Observe(host_contents_);
   host_contents_->SetDelegate(this);
-  SetViewType(host_contents_.get(), host_type);
+  SetViewType(host_contents_, host_type);
   main_frame_host_ = host_contents_->GetPrimaryMainFrame();
 
   // Listen for when an extension is unloaded from the same profile, as it may
@@ -77,6 +78,44 @@ ExtensionHost::ExtensionHost(const Extension* extension,
   ExtensionHostRegistry::Get(browser_context_)->ExtensionHostCreated(this);
 }
 
+ExtensionHost::ExtensionHost(ExtensionHostDelegate* delegate,
+                             const Extension* extension,
+                             content::BrowserContext* browser_context,
+                             content::WebContents* host_contents,
+                             const GURL& url,
+                             mojom::ViewType host_type)
+    : delegate_(delegate),
+      extension_(extension),
+      extension_id_(extension->id()),
+      browser_context_(browser_context),
+      host_contents_(host_contents),
+      initial_url_(url),
+      extension_host_type_(host_type) {
+  DCHECK(delegate);
+  DCHECK(browser_context);
+  DCHECK(host_contents);
+
+  // Not used for panels, see PanelHost.
+  DCHECK(host_type == mojom::ViewType::kExtensionBackgroundPage ||
+         host_type == mojom::ViewType::kExtensionDialog ||
+         host_type == mojom::ViewType::kExtensionPopup);
+
+  content::WebContentsObserver::Observe(host_contents_);
+  SetViewType(host_contents_, host_type);
+
+  main_frame_host_ = host_contents_->GetMainFrame();
+
+  // Listen for when an extension is unloaded from the same profile, as it may
+  // be the same extension that this points to.
+  ExtensionRegistry::Get(browser_context_)->AddObserver(this);
+
+  // Set up web contents observers and pref observers.
+  delegate_->OnExtensionHostCreated(host_contents_);
+
+  ExtensionWebContentsObserver::GetForWebContents(host_contents_)->
+      dispatcher()->set_delegate(this);
+}
+
 ExtensionHost::~ExtensionHost() {
   ExtensionRegistry::Get(browser_context_)->RemoveObserver(this);
 
diff --git extensions/browser/extension_host.h extensions/browser/extension_host.h
index dd22bbc07fb52..c695ece6b1a47 100644
--- extensions/browser/extension_host.h
+++ extensions/browser/extension_host.h
@@ -53,6 +53,12 @@ class ExtensionHost : public DeferredStartRenderHost,
                 content::SiteInstance* site_instance,
                 const GURL& url,
                 mojom::ViewType host_type);
+  ExtensionHost(ExtensionHostDelegate* delegate,
+                const Extension* extension,
+                content::BrowserContext* browser_context,
+                content::WebContents* host_contents,
+                const GURL& url,
+                mojom::ViewType host_type);
 
   ExtensionHost(const ExtensionHost&) = delete;
   ExtensionHost& operator=(const ExtensionHost&) = delete;
@@ -63,7 +69,7 @@ class ExtensionHost : public DeferredStartRenderHost,
   const Extension* extension() const { return extension_; }
 
   const std::string& extension_id() const { return extension_id_; }
-  content::WebContents* host_contents() const { return host_contents_.get(); }
+  content::WebContents* host_contents() const { return host_contents_; }
   content::RenderFrameHost* main_frame_host() const { return main_frame_host_; }
   content::RenderProcessHost* render_process_host() const;
   bool has_loaded_once() const { return has_loaded_once_; }
@@ -188,7 +194,8 @@ class ExtensionHost : public DeferredStartRenderHost,
   raw_ptr<content::BrowserContext> browser_context_;
 
   // The host for our HTML content.
-  std::unique_ptr<content::WebContents> host_contents_;
+  std::unique_ptr<content::WebContents> host_contents_owned_;
+  content::WebContents* host_contents_;
 
   // A pointer to the current or speculative main frame in `host_contents_`. We
   // can't access this frame through the `host_contents_` directly as it does
diff --git extensions/browser/extensions_browser_client.h extensions/browser/extensions_browser_client.h
index f87cf6d3bb4b6..bd2f3b576b004 100644
--- extensions/browser/extensions_browser_client.h
+++ extensions/browser/extensions_browser_client.h
@@ -31,6 +31,7 @@
 #include "url/gurl.h"
 
 class ExtensionFunctionRegistry;
+class GURL;
 class PrefService;
 
 namespace base {
@@ -73,6 +74,7 @@ class ComponentExtensionResourceManager;
 class Extension;
 class ExtensionCache;
 class ExtensionError;
+class ExtensionHost;
 class ExtensionHostDelegate;
 class ExtensionSet;
 class ExtensionSystem;
@@ -219,6 +221,14 @@ class ExtensionsBrowserClient {
   virtual std::unique_ptr<ExtensionHostDelegate>
   CreateExtensionHostDelegate() = 0;
 
+  // CEF creates a custom ExtensionHost for background pages. If the return
+  // value is true and |host| is NULL then fail the background host creation.
+  virtual bool CreateBackgroundExtensionHost(
+      const Extension* extension,
+      content::BrowserContext* browser_context,
+      const GURL& url,
+      ExtensionHost** host) { return false; }
+
   // Returns true if the client version has updated since the last run. Called
   // once each time the extensions system is loaded per browser_context. The
   // implementation may wish to use the BrowserContext to record the current
diff --git extensions/browser/process_manager.cc extensions/browser/process_manager.cc
index 4d09fbd93cd97..e6bc21a1adf49 100644
--- extensions/browser/process_manager.cc
+++ extensions/browser/process_manager.cc
@@ -393,9 +393,17 @@ bool ProcessManager::CreateBackgroundHost(const Extension* extension,
     return true;  // TODO(kalman): return false here? It might break things...
 
   DVLOG(1) << "CreateBackgroundHost " << extension->id();
-  ExtensionHost* host =
+  ExtensionHost* host = nullptr;
+  if (ExtensionsBrowserClient::Get()->CreateBackgroundExtensionHost(
+          extension, browser_context_, url, &host) && !host) {
+    // Explicitly fail if the client can't create the host.
+    return false;
+  }
+  if (!host) {
+    host =
       new ExtensionHost(extension, GetSiteInstanceForURL(url).get(), url,
                         mojom::ViewType::kExtensionBackgroundPage);
+  }
   host->CreateRendererSoon();
   OnBackgroundHostCreated(host);
   return true;