//components/surface_embed implements a mechanism to embed a content::WebContents (the inner/guest 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 guest content::WebContents, the frontend needs a unique identifier corresponding to that guest. This is managed by GuestContentsHandle.
On the C++ side, associate a handle with the guest WebContents and retrieve its assigned unique ID token (GuestId / base::UnguessableToken):
#include "components/guest_contents/browser/guest_contents_handle.h" // 1. Create or retrieve the handle for the target WebContents guest_contents::GuestContentsHandle* guest_handle = guest_contents::GuestContentsHandle::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 = guest_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 guest page on your WebUI frontend, simply instantiate the <embed> tag with your guest'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.guestId}"> </embed> `;
type: Must match kInternalPluginMimeType, which is "application/x-chromium-surface-embed".data-content-id: Must contain the serialized string representation of the guest_contents::GuestContentsHandle token corresponding to the nested WebContents (the content_id retrieved in Section 1.3).