//components/surface_embed implements a mechanism to embed a content::WebContents (the inner/embedded page) inside another content::WebContents (the outer/embedder page) by using a custom blink::WebPlugin. It is utilized by WebUI Browser to embed tab contents directly.
Currently, surface-embed is only enabled for chrome://webui-browser. If you wish to allow another frame or WebUI domain to use surface-embed, you must:
SurfaceEmbedHost in chrome_content_browser_client_receiver_bindings.cc:#include "components/surface_embed/common/features.h" #include "components/surface_embed/browser/surface_embed_host.h" // ... if (base::FeatureList::IsEnabled(surface_embed::features::kSurfaceEmbed)) { associated_registry.AddInterface<surface_embed::mojom::SurfaceEmbedHost>( base::BindRepeating( [](content::RenderFrameHost* render_frame_host, mojo::PendingAssociatedReceiver< surface_embed::mojom::SurfaceEmbedHost> receiver) { auto* web_ui = render_frame_host->GetWebUI(); // Security check: only allow surface-embed in WebUIBrowserUI. // Add your WebUIController here. if (!web_ui || !web_ui->GetController()->GetAs<WebUIBrowserUI>()) { return; } surface_embed::SurfaceEmbedHost::Create(render_frame_host, std::move(receiver)); }, &render_frame_host)); }
if (url.SchemeIs(content::kChromeUIScheme) && url.host() == chrome::kChromeUIYourCustomHost) { if (surface_embed::MaybeCreatePlugin(render_frame, params, plugin)) { return true; } }
By default, WebUI blocks <object>s and <embed>s. You must explicitly override the CSP object-src rule to allow 'self' via WebUIDataSource.
source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ObjectSrc, "object-src 'self';");
To embed a child content::WebContents, the frontend needs a unique identifier corresponding to that embedded WebContents. This is managed by SurfaceEmbedHandle.
On the C++ side, associate a handle with the embedded WebContents and retrieve its assigned unique ID token (base::UnguessableToken):
#include "components/surface_embed/browser/surface_embed_handle.h" // 1. Create or retrieve the handle for the target WebContents surface_embed::SurfaceEmbedHandle* embedded_handle = surface_embed::SurfaceEmbedHandle::CreateForWebContents(web_contents); // 2. Get the unique ID token and pass its serialized string to the frontend // (e.g. via Mojo message) std::string content_id = embedded_handle->id().ToString();
Surface Embed does not manage the lifecycle of WebContents. If the child WebContents is destroyed, the <embed> will become blank. If the <embed> is removed, the previously attached WebContents will be detached. Changing data-content-id on an existing plugin is not currently supported (crbug.com/561637127). To embed different contents, update data-content-id, then remove and restore the type attribute to force the plugin to be recreated with the new content ID.
To embed the child page on your WebUI frontend, simply instantiate the <embed> tag with your embedded WebContents's content ID.
<embed> ElementIn your template:
import {html} from '//resources/lit/v3_0/lit.rollup.js'; // Inside your custom WebUI component render() helper: return html` <embed class="content" type="application/x-chromium-surface-embed" data-content-id="${this.contentId}"> </embed> `;
type: Must match kInternalPluginMimeType, which is "application/x-chromium-surface-embed".data-content-id: Must contain the serialized string representation of the surface_embed::SurfaceEmbedHandle token corresponding to the nested WebContents (the content_id retrieved in Section 1.3).