components/web_modal/README.md
components/web_modal)The web_modal component manages web-contents modal dialogs in Chromium—prompts and dialogs scoped to a specific content::WebContents rather than the entire browser window (e.g., HTTP basic authentication, print preview, specialized permission prompts like File System Access confirmations or Page Embedded Permission Control modals, WebAuthn/passkey dialogs, and device choosers).
Because components/web_modal is decoupled from concrete desktop UI frameworks (Views browser windows, auxiliary bubbles, WebUI pickers, or embedders like CEF), containers rely on WebContentsModalDialogManagerDelegate and WebContentsModalDialogHost to manage dialog hosting, bounds clipping, input blocking, and visibility lifecycle.
Any UI container or window hosting a WebContents that may display web-modal dialogs must adhere to a strict four-part lifecycle contract:
CreateForWebContents):
Immediately upon WebContents creation, instantiate the per-WebContents dialog manager (web_modal::WebContentsModalDialogManager::CreateForWebContents(web_contents)).SetDelegate(delegate)):
Synchronously assign the container's delegate (manager->SetDelegate(delegate)) when attaching a WebContents to a window or view, strictly before any layout or activation runs.WebContentsModalDialogHost):
Implement WebContentsModalDialogManagerDelegate::GetWebContentsModalDialogHost() to supply a valid WebContentsModalDialogHost (ModalDialogHost).SetDelegate(nullptr)):
Explicitly clear the delegate (manager->SetDelegate(nullptr)) before tab reparenting between windows, C++ ownership hand-off (std::move), or window teardown.CreateForWebContents)Immediately upon WebContents creation (or during tab-helper initialization), the container must instantiate the per-WebContents dialog manager:
web_modal::WebContentsModalDialogManager::CreateForWebContents(web_contents);
WebContentsModalDialogManager is stored as a content::WebContentsUserData<WebContentsModalDialogManager> and is not automatically created by content::WebContents::Create().
When a web feature or page requests a web-modal prompt, call sites query:
web_modal::WebContentsModalDialogManager* manager =
web_modal::WebContentsModalDialogManager::FromWebContents(web_contents);
If CreateForWebContents() was never called on that WebContents, FromWebContents() returns nullptr, causing prompt requests to silently drop or trigger null-pointer crashes.
TabHelpers::AttachTabHelpers(): Calls web_modal::WebContentsModalDialogManager::CreateForWebContents(web_contents) for standard browser tabs upon initialization.SetDelegate(delegate))When attaching a WebContents to a window or container view, the container must synchronously assign its delegate before any layout or tab activation occurs:
web_modal::WebContentsModalDialogManager::FromWebContents(web_contents)
->SetDelegate(delegate);
Assigning the delegate synchronously prior to layout or activation prevents two classes of errors:
WebContents that already has an active dialog across windows. While detached between containers, an active dialog temporarily has no modal host. If the destination window defers delegate assignment until after its initial layout pass, open dialogs cannot query the new host's bounds during layout, leaving them unpositioned or anchored to stale geometry.As explicitly documented in Chromium's Browser::SetAsDelegate():
// The modal dialog delegate must be set during SetAsDelegate (not via a
// separate TabStripModelObserver) to ensure it is wired before layout
// triggered by OnActiveTabChanged.
Browser::SetAsDelegate(web_contents, true): Synchronously wires BrowserWindowModalDialogDelegate::From(this) when a tab is inserted into a browser window.WebContentsModalDialogHost)The delegate's GetWebContentsModalDialogHost(web_contents) method must return a valid web_modal::WebContentsModalDialogHost (implementing ModalDialogHost).
Any object acting as a WebContentsModalDialogHost must guarantee five core behaviors:
GetHostView()):
Must return the gfx::NativeView against which modal dialog widgets are parented and positioned.GetMaximumDialogSize()):
Must return the maximum bounding gfx::Size (typically matching the visible bounds of the WebContents container) so dialogs are clipped inside the web area or browser window.GetDialogPosition()):
Must return the gfx::Point origin relative to GetHostView() where the dialog of the given gfx::Size should be placed.OnPositionRequiresUpdate):
When the container window or view moves or resizes, the host must iterate through registered ModalDialogHostObserver instances and call OnPositionRequiresUpdate() so open dialogs recalculate their origin.OnHostDestroying):
Before the host object is destroyed, it must call OnHostDestroying() on all registered observers. If omitted, active dialog managers or open dialog widgets will retain dangling pointers to the destroyed host view.BrowserView::GetWebContentsModalDialogHost(): Entrypoint on standard browser Views windows. This delegates directly to BrowserViewLayout::GetWebContentsModalDialogHost(), which returns its internal BrowserModalDialogHostViews instance (dialog_host_).PaymentHandlerModalDialogManagerDelegate::GetWebContentsModalDialogHost(): Demonstrates how a custom secondary container delegates to its parent window's modal host.WebUIBubbleDialogView::GetWebContentsModalDialogHost(): Implements WebContentsModalDialogHost to position and clip web-modal dialogs within Top Chrome WebUI bubbles.SetDelegate(nullptr))Before a WebContents is reparented between windows, transferred to another container via C++ ownership hand-off (std::move), or left outliving its container window, the container must explicitly clear the delegate:
web_modal::WebContentsModalDialogManager::FromWebContents(web_contents)
->SetDelegate(nullptr);
| Circumstance | Why SetDelegate(nullptr) Is Critical | Canonical Reference |
|---|---|---|
1. Tab Detachment / Dragging (TabDetachedAtImpl) | While a tab is reparented between windows, it temporarily has no parent container. Unsetting prevents incoming prompts or layout updates during the drag from mutating or querying the source window. | Browser::TabDetachedAtImpl() calling SetAsDelegate(contents, false) |
2. Ownership Hand-off (std::move) | When a secondary flow (ProfilePickerSignInProvider, onboarding window) transfers a WebContents to a normal browser window, it must unset its delegate prior to hand-off so the manager does not retain a pointer to the old container view. | ProfilePickerSignInProvider::FinishFlow() calling ResetWebContentsDelegates() |
| 3. Container Teardown | WebContentsModalDialogManager stores raw_ptr<Delegate> delegate_. If a container window closes while the WebContents outlives it or tears down asynchronously, failing to clear SetDelegate(nullptr) triggers dangling pointer crashes (DANGLING_PTR_DETECTED). | DevToolsWindow::OnBrowserClosed() |
4. Tab Replacement (OnTabReplacedAt) | When swapping out an existing WebContents in a tab slot for a new one, unhook the delegate on the discarded WebContents before attaching the incoming one. | Browser::OnTabReplacedAt() |
Understanding how web-contents modal dialogs behave when the underlying WebContents navigates involves a clean division of responsibility between container hosts and dialog feature controllers:
WebContentsModalDialogManager is a content::WebContentsObserver. When a primary main frame commits a cross-site (!SameDomainOrHost(...)) navigation, DidFinishNavigation() automatically notifies observers (OnWillCloseOnNavigation) and closes all active and queued child dialogs (CloseAllDialogs()). Container implementers do not need any navigation-handling code.BFCache) Protection:
If a primary main frame navigates while a web-modal dialog is open, the manager automatically disables BFCache (DisabledReasonId::kModalDialog).PrintPreviewDialogController::DidFinishNavigation()) must observe WebContentsObserver directly and tear down their own prompt.DigitalIdentitySafetyInterstitialControllerDesktop::CloseOnNavigationObserver) can implement WebContentsModalDialogManager::Observer::OnWillCloseOnNavigation().// 1. CREATION: Immediately after creating a WebContents
web_modal::WebContentsModalDialogManager::CreateForWebContents(web_contents);
// 2. ATTACHMENT: Synchronously when attaching to window/container (before layout)
web_modal::WebContentsModalDialogManager::FromWebContents(web_contents)
->SetDelegate(this);
// 3. DIALOG HOSTING: Delegate implementation supplies the modal dialog host
web_modal::WebContentsModalDialogHost* GetWebContentsModalDialogHost(
content::WebContents* web_contents) override {
return modal_dialog_host_;
}
// 4. DETACHMENT / TEARDOWN: Before detach, move, or window destruction
web_modal::WebContentsModalDialogManager::FromWebContents(web_contents)
->SetDelegate(nullptr);