blob: 077f51bff6e8a9e04cd616cf3f78948bc66c23c5 [file] [view]
# Surface Embed User Guide
`//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.
---
## 1. Setup in C++ (Browser Process)
### 1.1. Allow Your Frame to Use Surface-Embed
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:
1. Enable the feature flag [kSurfaceEmbed](https://source.chromium.org/chromium/chromium/src/+/main:components/surface_embed/common/features.h;l=17;drc=3d12ee18ff8660d03d307fcfc4d7cfca9c793e46).
2. Register the binder for `SurfaceEmbedHost` in [chrome_browser_interface_binders_webui_parts_desktop.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/chrome_browser_interface_binders_webui_parts_desktop.cc;l=521;drc=3d12ee18ff8660d03d307fcfc4d7cfca9c793e46):
```cpp
#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));
}));
}
```
3. Update [ChromeContentRendererClient::OverrideCreatePlugin](https://source.chromium.org/chromium/chromium/src/+/main:chrome/renderer/chrome_content_renderer_client.cc;l=905-906;drc=a2f4b912ddca2b52a816da8603fe23fa5d1bf5a6)
to recognize your WebUI URL/host and allow the creation of the plugin:
```cpp
if (url.host() == chrome::kChromeUIYourCustomHost) {
if (surface_embed::MaybeCreatePlugin(render_frame, params, plugin)) {
return true;
}
}
```
### 1.2. Configure Content Security Policy (CSP)
By default, WebUI blocks `<object>`s and `<embed>`s. You must explicitly
override the CSP `object-src` rule to allow `'self'` via [WebUIDataSource](https://source.chromium.org/chromium/chromium/src/+/main:content/public/browser/web_ui_data_source.h;l=147;drc=d673b3a7277668f3e619493d8f91deca6616b07a).
```cpp
source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::ObjectSrc, "object-src 'self';");
```
### 1.3. How to Get the Content ID for a WebContents
To embed a guest `content::WebContents`, the frontend needs a unique identifier
corresponding to that guest. This is managed by [GuestContentsHandle](https://source.chromium.org/chromium/chromium/src/+/main:components/guest_contents/browser/guest_contents_handle.h;l=28;drc=d60f66038e8386eeee7708012e9b61c7ff8afb8d).
On the C++ side, associate a handle with the guest `WebContents` and retrieve
its assigned UUID (`GuestId` / `base::UnguessableToken`):
```cpp
#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.
---
## 2. Setup in HTML / JS (Frontend)
To embed the guest page on your WebUI frontend, simply instantiate the `<embed>`
tag with your guest's content ID.
### 2.1. Render the `<embed>` Element
In your template:
```typescript
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>
`;
```
### 2.2. Requirements for Attributes
* **`type`**: Must match [kInternalPluginMimeType](https://source.chromium.org/chromium/chromium/src/+/main:components/surface_embed/common/constants.h;l=11;drc=da966bf8542039f60d23ff8d922166ef30725b5d),
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.