components/surface_embed/USER_GUIDE.md
//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_browser_interface_binders_webui_parts_desktop.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)) {
map->Add<surface_embed::mojom::SurfaceEmbedHost>(base::BindRepeating(
[](content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<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));
}));
}
if (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 UUID (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 or set to a different content-id, the previously attached
WebContents will be detached.
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).When the data-content-id attribute changes, the custom plugin automatically
notices, parses the identifier, and communicates with the browser host to swap
the nested visual frame/surface instantly.