Back to Chromium

GuestView vs. SurfaceEmbed

components/surface_embed/guest_view_vs_surface_embed.md

151.0.7911.19.8 KB
Original Source

GuestView vs. SurfaceEmbed

GuestView and SurfaceEmbed are two different mechanisms in Chromium for nesting or embedding web content (a child document/view) inside an outer document (such as a WebUI page or Chrome App). This doc describes their differences.

Overview

GuestView

GuestView (found in //components/guest_view) is the implementation underpinning the <webview> tag in classic Chrome Apps, <controlledframe> in isolated web apps (IWA), and MimeHandlerView. It reuses the Out-of-process iframe (OOPIF) infrastructure. The embedding relationship is recorded in the browser process in content::FrameTree and WebContentsTreeNode.

Historically, it nested an inner content::WebContents inside an outer content::WebContents. Modern GuestView implementations are migrating towards a Multiple Page Architecture (MPArch) to avoid using inner WebContents and instead structure the guest as a separate page within a GuestPageHolder.

SurfaceEmbed

SurfaceEmbed (found in //components/surface_embed) is a modern, lightweight, and highly secure alternative. It embeds an independent content::WebContents (e.g., a browser tab's content) inside an outer WebUI or document using a blink::WebPlugin.

Rather than reproducing standard iframe frame/proxy structures, SurfaceEmbed only bridges the visual layer by binding the inner WebContents's RenderWidgetHostViewChildFrame directly to a compositor surface layer (cc::SurfaceLayer) managed by the renderer-side plugin and the browser side content::SurfaceEmbedConnector. It does not provide a scripting API between the parent and the child document.


Comparison Table

FeatureGuestView (<webview>)SurfaceEmbed (<embed>)
DOM Element<webview> custom HTML element<embed> with custom MIME type
Browser-side ContainerWebContents (traditional) or GuestPageHolder (MPArch)WebContents
Compositing AttachmentRemoteFrame + CrossProcessFrameConnector (reusing OOPIF)blink::WebPlugin + SurfaceEmbedConnector
OwnershipOuter frame owns the guest (Tightly coupled)Decoupled (User owns child WebContents)
Scripting APIs (e.g., postMessage)Fully supported (uses iframe-like remote frame proxies)None
Frontend APIExtensive (navigation, script injection, event listeners)None
Navigation ControlJS-driven (via src attribute)Browser-driven (via WebContents API)

Core Differences

1. Ownership Model

GuestView Ownership

In GuestView, the guest context is tightly coupled to the outer WebContents that embeds it.

  • Pre-MPArch: The outer WebContents owns the inner WebContents once attached.
  • MPArch: The guest's state is managed as a GuestPageHolder within the parent's FrameTree / Page context. Therefore, the guest page is owned and destroyed directly along with the parent frame/page hierarchy. Its lifetime cannot be easily separated or reparented.
  • Preloading Limitation: Because of this tight coupling, you cannot preload an embedded WebContents for <webview> and then attach it when needed.

SurfaceEmbed Ownership

Under SurfaceEmbed, the lifetimes of the outer frame and child WebContents are completely decoupled:

  • The child WebContents is typically spawned and owned by the browser-side client (e.g., the browser's tab strip or side panel coordinator), NOT the outer WebContents.
  • In SurfaceEmbedConnector, the connection to child is established via a raw_ptr<content::WebContents>. It does not govern the inner page's lifecycle.
  • Attachment is managed in the browser using the static helper content::SurfaceEmbedConnector::Attach and detachment is done via content::SurfaceEmbedConnector::Detach. This allows the inner WebContents to be detached, moved, and re-attached to a completely different browser window/WebUI without destroying the page or losing its state. This enables preloading and deferred attachment.

2. Container (WebContents vs. GuestPageHolder)

GuestView Containers

  • Historically, GuestView used an inner WebContents (attached via AttachAsInnerWebContents()).
  • In modern Chromium, to simplify the browser processes and improve security isolation, GuestViews are migrating to MPArch. Under MPArch, the guest does not get its own WebContents instance. Instead, it is represented as a separate Page hosted in a GuestPageHolder inside the outer WebContents's FrameTree. This abstracts WebContents operations away from the guest and restricts direct WebContents level API access (such as attaching arbitrary tab helpers).
  • Note: The GuestPageHolder work is currently behind a disabled-by-default feature flag.

SurfaceEmbed Containers

  • SurfaceEmbed retains the use of a full, independent content::WebContents instance (the child WebContents) for the embedded child.
  • Because the child remains a standard WebContents instance, the browser can attach arbitrary TabHelpers (e.g., find-in-page, autofill, permission managers, print preview, downloads) and interact directly with its navigation and lifecycle APIs.
  • However, unlike pre-MPArch GuestView, SurfaceEmbed does not route input/layout/compositor frames through a nested frame tree or RenderFrameProxyHost. Instead, content::SurfaceEmbedConnector coordinates the mapping. When the child creates its RenderWidgetHostViewChildFrame, the connector sends the FrameSinkId to the renderer's SurfaceEmbedWebPlugin, which embeds the surface using cc::SurfaceLayer.

3. postMessage and OOPIF Scripting APIs

GuestView Scripting

Because <webview> / GuestView embeds the guest frame using cross-process frame routing primitives (RenderFrameProxyHost in the browser, blink::RemoteFrame in the renderer), it inherits the standard HTML frame hierarchy connectivity.

  • This allows the parent document to acquire a reference to the child's contentWindow.
  • Scripts in the parent document can call postMessage to communicate with the child.
  • Security Risk: Reusing these general-purpose iframe-related primitives exposes a broad IPC attack surface. A compromise in the parent or child renderer may bypass isolation bounds if there're bugs in the frame proxy implementation. This is considered risky for a parent frame that contains sensitive information (e.g. the Browser UI).

SurfaceEmbed Scripting

SurfaceEmbed intentionally excludes all scripting and standard cross-frame communication APIs:

  • There is no contentWindow exposure and no JavaScript representation of the child page in the parent DOM window tree.
  • Scripting channels like postMessage or frame relation properties (like window.parent or window.opener) do not work and are completely blocked.
  • If communication is needed, the user must build their own custom messaging channel between the parent and child (e.g., via browser-side routing).
  • The interaction is solely visual and input-focused. This includes:
    1. The parent frame embeds the the plugin's surface.
    2. The browser routes mouse/keyboard input events and focus to the child view.
    3. The child frame posts its Compositor frame sink/surface to the plugin's cc::SurfaceLayer.

4. <webview>'s API vs. <embed>'s API

Navigation Control

  • <webview>: Initial load and navigation are done by setting the src attribute in JavaScript.
  • <embed>: Loading and navigation are done by calling the WebContents API on the embedded WebContents in the browser process.

Frontend API

The <webview> tag exposes a rich, comprehensive JavaScript API for scripting, navigation, and state inspection:

JavaScript
const webview = document.querySelector('webview');
webview.src = "https://example.com";
webview.addEventListener('contentload', () => { ... });
webview.executeScript({ code: "alert('Hello')" });
webview.insertCSS({ code: "body { background: red; }" });
webview.goBack();

These APIs allow the webpage to control navigation, intercept network requests, customize context menus, inject scripts, and listen to detailed page load/error states.

The <embed> tag used by SurfaceEmbed does not expose any programmatic API to the embedding page's JavaScript:

html
<embed type="application/x-chromium-surface-embed" data-content-id="[content-id]">
  • The content-id is generated by GuestContentsHandle::CreateForWebContents(web_contents).id().
  • The <embed> element is treated by JavaScript as a standard web plugin (similar to a PDF reader).
  • It does not have properties like .src, methods like .goBack(), or events like contentload.
  • Any navigation, history control, or scripting must be driven out-of-band on the browser side. For example, the parent WebUI communicates with its C++ WebUIController (using Mojo or chrome.send), and the browser-side code directly invokes the child WebContents's NavigationController.

5. Web Capabilities Differences

This is a non-exhaustive list of differences.

  • window.open(url, target):
    • <webview>: Will navigate the embedded page, even if the target is _blank or _top.
    • <embed>: Can customize the behavior by overriding WebContentsDelegate on the embedded WebContents (not possible with <webview>).
  • Intersection Observer API:
    • <webview>: The position of the <webview> in the parent document affects the visibility results for elements inside the guest.
    • <embed>: The positioning in the parent document does not affect the results inside the guest, as it is treated as an independent visual surface.
  • Screen Capturing:
    • <webview>: Cannot be captured (via navigator.mediaDevices) if the parent document is invisible (e.g., in an inactive tab).
    • <embed>: Can always be captured regardless of parent visibility.