blob: c6a54d5576de6bdc45926af465b43db6395d5ef0 [file] [view]
# 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
| Feature | GuestView (`<webview>`) | SurfaceEmbed (`<embed>`) |
| :--- | :--- | :--- |
| **DOM Element** | `<webview>` custom HTML element | `<embed>` with custom MIME type |
| **Browser-side Container** | `WebContents` (traditional) or `GuestPageHolder` (MPArch) | `WebContents` |
| **Compositing Attachment** | `RemoteFrame` + `CrossProcessFrameConnector` (reusing OOPIF) | `blink::WebPlugin` + `SurfaceEmbedConnector` |
| **Ownership** | Outer 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 API** | Extensive (navigation, script injection, event listeners) | **None** |
| **Navigation Control** | JS-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.