Back to Chromium

Surface Embed User Guide

components/surface_embed/USER_GUIDE.md

151.0.7916.15.1 KB
Original Source

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.
  2. Register the binder for SurfaceEmbedHost in chrome_browser_interface_binders_webui_parts_desktop.cc:
    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 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.

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.

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, 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.