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++

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. Register the binder for 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));
    }
    
  2. Update ChromeContentRendererClient::OverrideCreatePlugin to recognize your WebUI URL and allow the creation of the plugin:
    if (url.SchemeIs(content::kChromeUIScheme) &&
        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.

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


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:

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