From 7c4d3a12798549a82b22c675f8569ce0ec6ba9c6 Mon Sep 17 00:00:00 2001 From: Vipul Gupta Date: Tue, 9 Jun 2026 07:39:56 +0530 Subject: [PATCH 1/9] API Review: Frame-Level LaunchingExternalUriScheme Adds a frame-level LaunchingExternalUriScheme event on CoreWebView2Frame and a Handled property on CoreWebView2LaunchingExternalUriSchemeEventArgs. This lets host apps that embed multiple sub-applications as iframes attribute external-URI launches to a specific iframe via the event sender, and lets per-iframe handlers suppress the webview-level handler. Mirrors the existing ICoreWebView2Frame3.add_PermissionRequested + ICoreWebView2PermissionRequestedEventArgs2.Handled shape. --- specs/FrameLaunchingExternalUriScheme.md | 398 +++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 specs/FrameLaunchingExternalUriScheme.md diff --git a/specs/FrameLaunchingExternalUriScheme.md b/specs/FrameLaunchingExternalUriScheme.md new file mode 100644 index 000000000..4bad1f5c6 --- /dev/null +++ b/specs/FrameLaunchingExternalUriScheme.md @@ -0,0 +1,398 @@ +Frame-Level LaunchingExternalUriScheme +=== + +# Background + +WebView2 raises the `LaunchingExternalUriScheme` event on the +`CoreWebView2` when web content attempts to launch a URI scheme registered +with the OS as an external scheme handler (for example `mailto:`, `tel:`, +or a custom protocol). The host can intercept the event to suppress the +default WebView2 dialog, present its own consent UX, and `Cancel` the +launch. + +Today the event surfaces only `Uri`, `InitiatingOrigin`, +`IsUserInitiated`, and `Cancel`. There is no way for the host to tell +*which iframe* initiated the launch. Hosts that embed multiple +sub-applications as iframes within a single WebView2 — typically loaded +from a shared CDN host — cannot reliably attribute a launch to the +specific iframe that triggered it, because: + +1. Multiple iframes can be served from the same origin and the event + provides no signal beyond the origin. +2. The same iframe content may be presented in more than one host + surface (window, panel) and the host cannot route the consent prompt + to the correct surface. +3. Sandboxed and `srcdoc` iframes have opaque origins; the API reports + their parent's origin, making the iframe itself invisible. + +This spec proposes raising the `LaunchingExternalUriScheme` event +additionally on `CoreWebView2Frame` so hosts can register per-iframe +handlers. The iframe object is the `sender` of the event, giving direct +attribution without any extra `QueryInterface` or name lookup. A new +`Handled` property on the event args lets a frame-level handler suppress +the webview-level handler for that launch. + +The shape of this API mirrors +[`ICoreWebView2Frame3::add_PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) ++ +[`ICoreWebView2PermissionRequestedEventArgs2::Handled`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2permissionrequestedeventargs2#put_handled) +— see +[specs/IFramePermissionRequested.md](IFramePermissionRequested.md) for +the precedent. The +[`OriginalSourceFrameInfo` property added to `NewWindowRequested`](NewWindowSourceFrameInfo.md) +solves a related attribution problem for that event; the trade-offs +between the two shapes are summarized in the Appendix. + +We would appreciate your feedback. + + +# Description + +We propose extending `CoreWebView2Frame` with a new +`LaunchingExternalUriScheme` event. The event is raised when content in +the `CoreWebView2Frame` (or any of its descendant iframes that don't have +a closer tracked `CoreWebView2Frame` ancestor) attempts to launch an +external URI scheme. The sender of the event is the `CoreWebView2Frame` +itself, so the host can immediately attribute the launch to a specific +iframe. + +We also propose extending +`CoreWebView2LaunchingExternalUriSchemeEventArgs` with a new `Handled` +property. + +To maintain backwards compatibility, by default we plan to raise +`LaunchingExternalUriScheme` on both `CoreWebView2Frame` and +`CoreWebView2`. The `CoreWebView2Frame` event handlers will be invoked +first, before the `CoreWebView2` event handlers. If `Handled` is set to +`TRUE` from within the `CoreWebView2Frame` event handler, then the event +will not be raised on the `CoreWebView2`, and its event handlers will +not be invoked. + +For nested iframes (an iframe inside an iframe), the event surfaces on +the closest tracked `CoreWebView2Frame` in the ancestor chain — matching +the routing convention already used by +[`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested). + +The event args are shared between the two handler tiers — properties set +by the frame handler (`Cancel`, `Handled`) are visible to the +webview handler, and a `Deferral` taken in either tier blocks the launch +until completed. + +The existing `Cancel` semantics are preserved. `Handled` is orthogonal: +`Cancel` controls whether the URI is actually launched; `Handled` only +controls whether the webview-level handlers run for this event. + + +# Examples + +## Registering a per-iframe LaunchingExternalUriScheme handler + +A host that embeds multiple sub-applications in iframes can register a +handler per iframe to attribute external-URI launches to a specific +sub-app and present a consent prompt that names the right one. Setting +`Handled = TRUE` from the frame handler prevents the webview-level +handler from running again for the same event. + +### C++ + +```cpp +AppWindow* m_appWindow; +wil::com_ptr m_webview; +EventRegistrationToken m_frameCreatedToken = {}; +EventRegistrationToken m_frameLaunchingExternalUriSchemeToken = {}; + +void RegisterFrameLaunchingExternalUriSchemeHandler() +{ + auto webview4 = m_webview.try_query(); + if (!webview4) + { + return; + } + + CHECK_FAILURE(webview4->add_FrameCreated( + Callback( + [this](ICoreWebView2* sender, + ICoreWebView2FrameCreatedEventArgs* args) -> HRESULT + { + wil::com_ptr webviewFrame; + CHECK_FAILURE(args->get_Frame(&webviewFrame)); + + auto frame10 = webviewFrame + .try_query(); + if (!frame10) + { + return S_OK; + } + + CHECK_FAILURE(frame10->add_LaunchingExternalUriScheme( + Callback< + ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler>( + [this]( + ICoreWebView2Frame* frameSender, + ICoreWebView2LaunchingExternalUriSchemeEventArgs2* + args) -> HRESULT + { + // We avoid potential reentrancy by showing the + // dialog via a lambda run asynchronously outside + // of this event handler. Because we plan to take + // a deferral, set `Handled` to TRUE synchronously + // before the deferral so that the + // `CoreWebView2`-level handlers do not run. + CHECK_FAILURE(args->put_Handled(TRUE)); + + wil::com_ptr deferral; + CHECK_FAILURE(args->GetDeferral(&deferral)); + + wil::com_ptr sender( + frameSender); + + m_appWindow->RunAsync( + [sender, deferral, args] + { + wil::unique_cotaskmem_string frameName; + CHECK_FAILURE( + sender->get_Name(&frameName)); + wil::unique_cotaskmem_string uri; + CHECK_FAILURE(args->get_Uri(&uri)); + + std::wstring message = + L"The \""; + message += frameName.get(); + message += + L"\" iframe is trying to launch " + L"the external scheme handler for "; + message += uri.get(); + message += + L".\n\nDo you want to allow it?"; + + int response = MessageBox( + nullptr, message.c_str(), + L"Launching External URI Scheme", + MB_YESNO | MB_ICONQUESTION); + CHECK_FAILURE(args->put_Cancel( + response == IDYES ? FALSE : TRUE)); + CHECK_FAILURE(deferral->Complete()); + }); + return S_OK; + }) + .Get(), + &m_frameLaunchingExternalUriSchemeToken)); + return S_OK; + }) + .Get(), + &m_frameCreatedToken)); +} +``` + +### C# + +```c# +private WebView2 m_webview; + +void RegisterFrameLaunchingExternalUriSchemeHandler() +{ + m_webview.CoreWebView2.FrameCreated += (sender, frameCreatedArgs) => + { + frameCreatedArgs.Frame.LaunchingExternalUriScheme += + async (frameSender, args) => + { + // Because we plan to await asynchronous work below, set + // `Handled` synchronously before taking the deferral so that + // the `CoreWebView2`-level handlers do not run. + args.Handled = true; + + CoreWebView2Deferral deferral = args.GetDeferral(); + using (deferral) + { + string message = $"The \"{frameSender.Name}\" iframe is " + + $"trying to launch the external scheme handler for " + + $"{args.Uri}.\n\nDo you want to allow it?"; + + MessageBoxResult selection = MessageBox.Show( + message, + "Launching External URI Scheme", + MessageBoxButton.YesNo); + + args.Cancel = selection != MessageBoxResult.Yes; + } + }; + }; +} +``` + + +# API Details + +## Win32 C++ + +```idl +/// This is an extension of the `ICoreWebView2Frame` interface that +/// surfaces the `LaunchingExternalUriScheme` event at the iframe level. +/// Host apps can subscribe per-iframe to attribute external URI scheme +/// launches to a specific iframe even when multiple frames share the +/// same origin. +[uuid(0e3c31b7-bbca-5216-a2d1-40e7a211f0ab), object, pointer_default(unique)] +interface ICoreWebView2ExperimentalFrame10 : IUnknown { + /// Add an event handler for the `LaunchingExternalUriScheme` event. + /// `LaunchingExternalUriScheme` is raised when content in this + /// `CoreWebView2Frame` (or any of its descendant iframes that don't + /// have a closer tracked `CoreWebView2Frame` ancestor) attempts to + /// launch a URI registered with the OS as an external scheme handler. + /// + /// This relates to the `LaunchingExternalUriScheme` event on the + /// `CoreWebView2`. For an iframe-initiated launch the + /// `CoreWebView2Frame`'s event handlers are invoked before the + /// `CoreWebView2`'s event handlers. If the `Handled` property of the + /// `ICoreWebView2LaunchingExternalUriSchemeEventArgs2` is set to `TRUE` + /// within the `CoreWebView2Frame` event handler, then the event will + /// not be raised on the `CoreWebView2`, and its event handlers will + /// not be invoked. + /// + /// If a deferral is not taken on the event args, the external URI + /// scheme launch is blocked until the event handler returns. If a + /// deferral is taken, the launch is blocked until the deferral is + /// completed. To suppress the `CoreWebView2`-level event handlers, + /// `Handled` must be set synchronously before any deferral is taken. + HRESULT add_LaunchingExternalUriScheme( + [in] ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler* + eventHandler, + [out] EventRegistrationToken* token); + + /// Removes an event handler previously added with + /// `add_LaunchingExternalUriScheme`. + HRESULT remove_LaunchingExternalUriScheme( + [in] EventRegistrationToken token); +} + +/// Receives `LaunchingExternalUriScheme` events raised on +/// `CoreWebView2Frame`. +[uuid(8d0a4bee-a888-50bc-8088-a71678fd3af3), object, pointer_default(unique)] +interface ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler + : IUnknown { + /// Provides the event args for the corresponding event. + HRESULT Invoke( + [in] ICoreWebView2Frame* sender, + [in] ICoreWebView2LaunchingExternalUriSchemeEventArgs2* args); +} + +/// This is a continuation of the +/// `ICoreWebView2LaunchingExternalUriSchemeEventArgs` interface that +/// adds the `Handled` property. +[uuid(126db12c-f6dc-51b7-afa4-3eecb5304b9f), object, pointer_default(unique)] +interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2 + : ICoreWebView2LaunchingExternalUriSchemeEventArgs { + /// By default, both the `LaunchingExternalUriScheme` event handlers on + /// the `CoreWebView2Frame` and the `CoreWebView2` will be invoked, + /// with the `CoreWebView2Frame` event handlers invoked first. The host + /// may set this flag to `TRUE` within the `CoreWebView2Frame` event + /// handlers to prevent the remaining `CoreWebView2` event handlers + /// from being invoked. + /// + /// If a deferral is taken on the event args, then you must + /// synchronously set `Handled` to `TRUE` prior to taking your deferral + /// to prevent the `CoreWebView2`'s event handlers from being invoked. + [propget] HRESULT Handled([out, retval] BOOL* value); + + /// Sets the `Handled` property. + [propput] HRESULT Handled([in] BOOL value); +} +``` + +## .NET, WinRT + +```c# +namespace Microsoft.Web.WebView2.Core +{ + runtimeclass CoreWebView2LaunchingExternalUriSchemeEventArgs + { + // ... + + [interface_name( + "Microsoft.Web.WebView2.Core.ICoreWebView2LaunchingExternalUriSchemeEventArgs2")] + { + [doc_string( + "The host may set this flag to TRUE to prevent the " + "LaunchingExternalUriScheme event from firing on the " + "CoreWebView2 as well. By default, both the " + "LaunchingExternalUriScheme on the CoreWebView2Frame " + "and the CoreWebView2 will be raised.")] + Boolean Handled { get; set; }; + } + } + + runtimeclass CoreWebView2Frame + { + // ... + + [interface_name( + "Microsoft.Web.WebView2.Core.ICoreWebView2ExperimentalFrame10")] + { + [doc_string( + "LaunchingExternalUriScheme is raised when content in " + "this CoreWebView2Frame (or any of its descendant " + "iframes that don't have a closer tracked " + "CoreWebView2Frame ancestor) attempts to launch a URI " + "registered with the OS as an external scheme handler. " + "The CoreWebView2Frame's event handlers are invoked " + "before the CoreWebView2's event handlers; set Handled " + "to TRUE in the frame handler to suppress the " + "webview-level handlers.")] + event Windows.Foundation.TypedEventHandler< + CoreWebView2Frame, + CoreWebView2LaunchingExternalUriSchemeEventArgs> + LaunchingExternalUriScheme; + } + } +} +``` + + +# Appendix + +## Alternative considered: `OriginalSourceFrameInfo` on the webview-level args + +We considered surfacing the initiating frame on the existing webview-level +event via an `OriginalSourceFrameInfo` property, mirroring the pattern +shipped on +[`ICoreWebView2NewWindowRequestedEventArgs3`](NewWindowSourceFrameInfo.md). +That approach delivers the iframe identity to a single webview-level +handler. + +We chose the frame-level event instead because it: + +* aligns with the per-frame ergonomic already shipped for + [`PermissionRequested`](IFramePermissionRequested.md) and + `ScreenCaptureStarting`; +* allows independent handler registration per iframe, decoupling default + webview-level policy from per-iframe rules; +* makes the iframe object directly available as the event `sender`, + removing the need for a `QueryInterface` on the args; and +* together with `Handled` lets a frame handler cleanly suppress the + webview handler — enabling allowlist / default-deny patterns where the + webview-level handler is the default policy and per-iframe handlers + override it. + +The two approaches are not mutually exclusive — `OriginalSourceFrameInfo` +on the args could be added later if a single-handler tier is preferred +for some scenarios. + +## Nested iframes + +The event surfaces on the closest tracked `CoreWebView2Frame` in the +initiating iframe's ancestor chain. This matches the routing convention +already used by +[`CoreWebView2Frame.PermissionRequested`](IFramePermissionRequested.md): +the host registers `FrameCreated` handlers (top-level iframes only by +default; or use +[`ICoreWebView2Frame7.add_FrameCreated`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame7#add_framecreated) +to track nested iframes), and `LaunchingExternalUriScheme` is delivered +to the closest tracked iframe up the chain. + +## Same-origin / cross-origin iframes + +The event fires regardless of whether the iframe shares an origin with +its parent. Cross-origin iframes without a user gesture still follow +existing WebView2 behavior: the external-URI launch is blocked and the +event is not raised, matching the +[`CoreWebView2.LaunchingExternalUriScheme`](LaunchingExternalUriScheme.md) +contract. From c6263359d3283b6682307256ccbff8bd30cb6e49 Mon Sep 17 00:00:00 2001 From: Vipul Gupta Date: Tue, 9 Jun 2026 07:56:58 +0530 Subject: [PATCH 2/9] Align spec with WebView2Feedback conventions --- specs/FrameLaunchingExternalUriScheme.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/specs/FrameLaunchingExternalUriScheme.md b/specs/FrameLaunchingExternalUriScheme.md index 4bad1f5c6..41f42400d 100644 --- a/specs/FrameLaunchingExternalUriScheme.md +++ b/specs/FrameLaunchingExternalUriScheme.md @@ -225,7 +225,11 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() ## Win32 C++ -```idl +```cpp +interface ICoreWebView2ExperimentalFrame10; +interface ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler; +interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2; + /// This is an extension of the `ICoreWebView2Frame` interface that /// surfaces the `LaunchingExternalUriScheme` event at the iframe level. /// Host apps can subscribe per-iframe to attribute external URI scheme @@ -298,7 +302,7 @@ interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2 } ``` -## .NET, WinRT +## .NET/C# ```c# namespace Microsoft.Web.WebView2.Core From d12e9674e6bb719f1c595a488099d64b06fa00e6 Mon Sep 17 00:00:00 2001 From: Vipul Gupta Date: Tue, 9 Jun 2026 08:03:56 +0530 Subject: [PATCH 3/9] Use learn.microsoft.com URLs instead of relative spec .md links --- specs/FrameLaunchingExternalUriScheme.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/specs/FrameLaunchingExternalUriScheme.md b/specs/FrameLaunchingExternalUriScheme.md index 41f42400d..0a6711d11 100644 --- a/specs/FrameLaunchingExternalUriScheme.md +++ b/specs/FrameLaunchingExternalUriScheme.md @@ -36,12 +36,10 @@ The shape of this API mirrors [`ICoreWebView2Frame3::add_PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) + [`ICoreWebView2PermissionRequestedEventArgs2::Handled`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2permissionrequestedeventargs2#put_handled) -— see -[specs/IFramePermissionRequested.md](IFramePermissionRequested.md) for -the precedent. The -[`OriginalSourceFrameInfo` property added to `NewWindowRequested`](NewWindowSourceFrameInfo.md) -solves a related attribution problem for that event; the trade-offs -between the two shapes are summarized in the Appendix. +. The +[`OriginalSourceFrameInfo` property on `ICoreWebView2NewWindowRequestedEventArgs3`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2newwindowrequestedeventargs3#get_originalsourceframeinfo) +solves a related attribution problem for the `NewWindowRequested` event; +the trade-offs between the two shapes are summarized in the Appendix. We would appreciate your feedback. @@ -358,15 +356,16 @@ namespace Microsoft.Web.WebView2.Core We considered surfacing the initiating frame on the existing webview-level event via an `OriginalSourceFrameInfo` property, mirroring the pattern shipped on -[`ICoreWebView2NewWindowRequestedEventArgs3`](NewWindowSourceFrameInfo.md). +[`ICoreWebView2NewWindowRequestedEventArgs3`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2newwindowrequestedeventargs3#get_originalsourceframeinfo). That approach delivers the iframe identity to a single webview-level handler. We chose the frame-level event instead because it: * aligns with the per-frame ergonomic already shipped for - [`PermissionRequested`](IFramePermissionRequested.md) and - `ScreenCaptureStarting`; + [`PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) + and + [`ScreenCaptureStarting`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame6#add_screencapturestarting); * allows independent handler registration per iframe, decoupling default webview-level policy from per-iframe rules; * makes the iframe object directly available as the event `sender`, @@ -385,7 +384,7 @@ for some scenarios. The event surfaces on the closest tracked `CoreWebView2Frame` in the initiating iframe's ancestor chain. This matches the routing convention already used by -[`CoreWebView2Frame.PermissionRequested`](IFramePermissionRequested.md): +[`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested): the host registers `FrameCreated` handlers (top-level iframes only by default; or use [`ICoreWebView2Frame7.add_FrameCreated`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame7#add_framecreated) @@ -398,5 +397,5 @@ The event fires regardless of whether the iframe shares an origin with its parent. Cross-origin iframes without a user gesture still follow existing WebView2 behavior: the external-URI launch is blocked and the event is not raised, matching the -[`CoreWebView2.LaunchingExternalUriScheme`](LaunchingExternalUriScheme.md) +[`CoreWebView2.LaunchingExternalUriScheme`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2_18#add_launchingexternalurischeme) contract. From f307ad9fdb23b9610b84692dabf51d9019a5b664 Mon Sep 17 00:00:00 2001 From: Vipul Gupta Date: Tue, 16 Jun 2026 16:09:39 +0530 Subject: [PATCH 4/9] Rewrite spec in Windows API documentation style (voice, tone, grammar) --- specs/FrameLaunchingExternalUriScheme.md | 325 +++++++++++------------ 1 file changed, 151 insertions(+), 174 deletions(-) diff --git a/specs/FrameLaunchingExternalUriScheme.md b/specs/FrameLaunchingExternalUriScheme.md index 0a6711d11..361abdd42 100644 --- a/specs/FrameLaunchingExternalUriScheme.md +++ b/specs/FrameLaunchingExternalUriScheme.md @@ -1,97 +1,86 @@ -Frame-Level LaunchingExternalUriScheme -=== - # Background -WebView2 raises the `LaunchingExternalUriScheme` event on the -`CoreWebView2` when web content attempts to launch a URI scheme registered -with the OS as an external scheme handler (for example `mailto:`, `tel:`, -or a custom protocol). The host can intercept the event to suppress the -default WebView2 dialog, present its own consent UX, and `Cancel` the -launch. - -Today the event surfaces only `Uri`, `InitiatingOrigin`, -`IsUserInitiated`, and `Cancel`. There is no way for the host to tell -*which iframe* initiated the launch. Hosts that embed multiple -sub-applications as iframes within a single WebView2 — typically loaded -from a shared CDN host — cannot reliably attribute a launch to the -specific iframe that triggered it, because: - -1. Multiple iframes can be served from the same origin and the event - provides no signal beyond the origin. -2. The same iframe content may be presented in more than one host - surface (window, panel) and the host cannot route the consent prompt - to the correct surface. -3. Sandboxed and `srcdoc` iframes have opaque origins; the API reports - their parent's origin, making the iframe itself invisible. - -This spec proposes raising the `LaunchingExternalUriScheme` event -additionally on `CoreWebView2Frame` so hosts can register per-iframe -handlers. The iframe object is the `sender` of the event, giving direct -attribution without any extra `QueryInterface` or name lookup. A new -`Handled` property on the event args lets a frame-level handler suppress -the webview-level handler for that launch. - -The shape of this API mirrors +WebView2 raises the `LaunchingExternalUriScheme` event on `CoreWebView2` +when web content attempts to launch a URI scheme registered with the OS as +an external scheme handler (for example, `mailto:`, `tel:`, or a custom +protocol). The host can handle this event to suppress the default WebView2 +dialog, present its own consent UI, and set `Cancel` to control whether the +URI is launched. + +The event currently exposes `Uri`, `InitiatingOrigin`, `IsUserInitiated`, +and `Cancel`. It does not identify the originating iframe. Hosts that embed +multiple sub-applications as iframes within a single WebView2 — often served +from a shared origin — cannot reliably attribute a launch to the iframe that +triggered it, because: + +1. Multiple iframes can be served from the same origin, and the event + exposes no identifier beyond the origin. +2. The same iframe content may be presented in more than one host surface + (window or panel), and the host cannot route the consent prompt to the + correct surface. +3. Sandboxed and `srcdoc` iframes have opaque origins. The API reports the + parent origin, making the initiating iframe indistinguishable. + +To enable iframe-level attribution, the `LaunchingExternalUriScheme` event +is also raised on `CoreWebView2Frame`. The host can register a handler per +iframe, and the iframe is the `sender` of the event, providing direct +attribution without an additional `QueryInterface` or name lookup. A new +`Handled` property on the event args allows a frame-level handler to suppress +the webview-level handlers for that launch. + +This design follows the per-frame event model used by [`ICoreWebView2Frame3::add_PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) -+ -[`ICoreWebView2PermissionRequestedEventArgs2::Handled`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2permissionrequestedeventargs2#put_handled) -. The -[`OriginalSourceFrameInfo` property on `ICoreWebView2NewWindowRequestedEventArgs3`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2newwindowrequestedeventargs3#get_originalsourceframeinfo) -solves a related attribution problem for the `NewWindowRequested` event; -the trade-offs between the two shapes are summarized in the Appendix. - -We would appreciate your feedback. +and +[`ICoreWebView2PermissionRequestedEventArgs2::Handled`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2permissionrequestedeventargs2#put_handled). +A related attribution mechanism, +[`OriginalSourceFrameInfo` on `ICoreWebView2NewWindowRequestedEventArgs3`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2newwindowrequestedeventargs3#get_originalsourceframeinfo), +addresses the same problem for the `NewWindowRequested` event. The trade-offs +between the two approaches are described in the Appendix. # Description -We propose extending `CoreWebView2Frame` with a new -`LaunchingExternalUriScheme` event. The event is raised when content in -the `CoreWebView2Frame` (or any of its descendant iframes that don't have -a closer tracked `CoreWebView2Frame` ancestor) attempts to launch an -external URI scheme. The sender of the event is the `CoreWebView2Frame` -itself, so the host can immediately attribute the launch to a specific -iframe. +The `LaunchingExternalUriScheme` event is raised on `CoreWebView2Frame` when +content in that frame, or in a descendant iframe that does not have a closer +tracked `CoreWebView2Frame` ancestor, attempts to launch an external URI +scheme. The `sender` of the event is the `CoreWebView2Frame`, so the host can +attribute the launch to a specific iframe. -We also propose extending -`CoreWebView2LaunchingExternalUriSchemeEventArgs` with a new `Handled` +`CoreWebView2LaunchingExternalUriSchemeEventArgs` exposes a new `Handled` property. -To maintain backwards compatibility, by default we plan to raise -`LaunchingExternalUriScheme` on both `CoreWebView2Frame` and -`CoreWebView2`. The `CoreWebView2Frame` event handlers will be invoked -first, before the `CoreWebView2` event handlers. If `Handled` is set to -`TRUE` from within the `CoreWebView2Frame` event handler, then the event -will not be raised on the `CoreWebView2`, and its event handlers will -not be invoked. - -For nested iframes (an iframe inside an iframe), the event surfaces on -the closest tracked `CoreWebView2Frame` in the ancestor chain — matching -the routing convention already used by +By default, the event is raised on both `CoreWebView2Frame` and +`CoreWebView2`. Frame-level handlers are invoked before webview-level +handlers. If `Handled` is set to `TRUE` in a frame-level handler, the event +is not raised on `CoreWebView2`, and its handlers are not invoked. This +preserves backward compatibility for hosts that handle the event only on +`CoreWebView2`. + +For nested iframes, the event is raised on the closest tracked +`CoreWebView2Frame` in the initiating iframe's ancestor chain, matching the +routing used by [`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested). -The event args are shared between the two handler tiers — properties set -by the frame handler (`Cancel`, `Handled`) are visible to the -webview handler, and a `Deferral` taken in either tier blocks the launch -until completed. +The event args are shared between the two handler tiers. Properties set by +the frame-level handler, including `Cancel` and `Handled`, are visible to +webview-level handlers. A `Deferral` taken in either tier blocks the launch +until the deferral is completed. -The existing `Cancel` semantics are preserved. `Handled` is orthogonal: -`Cancel` controls whether the URI is actually launched; `Handled` only -controls whether the webview-level handlers run for this event. +`Cancel` controls whether the URI is launched. `Handled` controls whether +webview-level handlers are invoked. The two are independent. # Examples ## Registering a per-iframe LaunchingExternalUriScheme handler -A host that embeds multiple sub-applications in iframes can register a -handler per iframe to attribute external-URI launches to a specific -sub-app and present a consent prompt that names the right one. Setting -`Handled = TRUE` from the frame handler prevents the webview-level -handler from running again for the same event. +A host that embeds multiple sub-applications in iframes can register a handler +per iframe to attribute external URI launches to a specific sub-application +and present a consent prompt that names the correct one. Setting +`Handled = TRUE` in the frame-level handler prevents the webview-level handler +from being invoked for the same event. -### C++ +### Win32 C++ ```cpp AppWindow* m_appWindow; @@ -130,12 +119,12 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() ICoreWebView2LaunchingExternalUriSchemeEventArgs2* args) -> HRESULT { - // We avoid potential reentrancy by showing the - // dialog via a lambda run asynchronously outside - // of this event handler. Because we plan to take - // a deferral, set `Handled` to TRUE synchronously - // before the deferral so that the - // `CoreWebView2`-level handlers do not run. + // Avoid reentrancy by scheduling the dialog + // asynchronously, outside this event handler. + // Because a deferral is taken, set `Handled` to + // TRUE synchronously before taking the deferral + // so that the `CoreWebView2`-level handlers are + // not invoked. CHECK_FAILURE(args->put_Handled(TRUE)); wil::com_ptr deferral; @@ -153,15 +142,13 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() wil::unique_cotaskmem_string uri; CHECK_FAILURE(args->get_Uri(&uri)); - std::wstring message = - L"The \""; + std::wstring message = L"The \""; message += frameName.get(); message += - L"\" iframe is trying to launch " - L"the external scheme handler for "; + L"\" iframe is attempting to launch " + L"an external URI scheme for "; message += uri.get(); - message += - L".\n\nDo you want to allow it?"; + message += L".\n\nAllow this action?"; int response = MessageBox( nullptr, message.c_str(), @@ -182,7 +169,7 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() } ``` -### C# +### .NET C# ```c# private WebView2 m_webview; @@ -194,17 +181,17 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() frameCreatedArgs.Frame.LaunchingExternalUriScheme += async (frameSender, args) => { - // Because we plan to await asynchronous work below, set - // `Handled` synchronously before taking the deferral so that - // the `CoreWebView2`-level handlers do not run. + // Because asynchronous work is awaited below, set `Handled` + // synchronously before taking the deferral so that the + // `CoreWebView2`-level handlers are not invoked. args.Handled = true; CoreWebView2Deferral deferral = args.GetDeferral(); using (deferral) { string message = $"The \"{frameSender.Name}\" iframe is " + - $"trying to launch the external scheme handler for " + - $"{args.Uri}.\n\nDo you want to allow it?"; + $"attempting to launch an external URI scheme for " + + $"{args.Uri}.\n\nAllow this action?"; MessageBoxResult selection = MessageBox.Show( message, @@ -228,33 +215,29 @@ interface ICoreWebView2ExperimentalFrame10; interface ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler; interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2; -/// This is an extension of the `ICoreWebView2Frame` interface that -/// surfaces the `LaunchingExternalUriScheme` event at the iframe level. -/// Host apps can subscribe per-iframe to attribute external URI scheme -/// launches to a specific iframe even when multiple frames share the -/// same origin. +/// Extends the `ICoreWebView2Frame` interface to expose the +/// `LaunchingExternalUriScheme` event at the iframe level. Host applications +/// can subscribe per iframe to attribute external URI scheme launches to a +/// specific iframe, even when multiple frames share the same origin. [uuid(0e3c31b7-bbca-5216-a2d1-40e7a211f0ab), object, pointer_default(unique)] interface ICoreWebView2ExperimentalFrame10 : IUnknown { - /// Add an event handler for the `LaunchingExternalUriScheme` event. - /// `LaunchingExternalUriScheme` is raised when content in this - /// `CoreWebView2Frame` (or any of its descendant iframes that don't - /// have a closer tracked `CoreWebView2Frame` ancestor) attempts to - /// launch a URI registered with the OS as an external scheme handler. + /// Adds an event handler for the `LaunchingExternalUriScheme` event. The + /// event is raised when content in this `CoreWebView2Frame`, or in a + /// descendant iframe that does not have a closer tracked + /// `CoreWebView2Frame` ancestor, attempts to launch a URI registered with + /// the OS as an external scheme handler. /// - /// This relates to the `LaunchingExternalUriScheme` event on the - /// `CoreWebView2`. For an iframe-initiated launch the - /// `CoreWebView2Frame`'s event handlers are invoked before the - /// `CoreWebView2`'s event handlers. If the `Handled` property of the + /// This event corresponds to `CoreWebView2.LaunchingExternalUriScheme`. For + /// an iframe-initiated launch, `CoreWebView2Frame` handlers are invoked + /// before `CoreWebView2` handlers. If the `Handled` property on /// `ICoreWebView2LaunchingExternalUriSchemeEventArgs2` is set to `TRUE` - /// within the `CoreWebView2Frame` event handler, then the event will - /// not be raised on the `CoreWebView2`, and its event handlers will - /// not be invoked. + /// within a `CoreWebView2Frame` handler, the event is not raised on + /// `CoreWebView2`, and its handlers are not invoked. /// - /// If a deferral is not taken on the event args, the external URI - /// scheme launch is blocked until the event handler returns. If a - /// deferral is taken, the launch is blocked until the deferral is - /// completed. To suppress the `CoreWebView2`-level event handlers, - /// `Handled` must be set synchronously before any deferral is taken. + /// If a deferral is not taken, the external URI scheme launch is blocked + /// until the handler returns. If a deferral is taken, the launch is blocked + /// until the deferral is completed. To prevent `CoreWebView2` handlers from + /// being invoked, set `Handled` synchronously before taking a deferral. HRESULT add_LaunchingExternalUriScheme( [in] ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler* eventHandler, @@ -264,10 +247,9 @@ interface ICoreWebView2ExperimentalFrame10 : IUnknown { /// `add_LaunchingExternalUriScheme`. HRESULT remove_LaunchingExternalUriScheme( [in] EventRegistrationToken token); -} +}; -/// Receives `LaunchingExternalUriScheme` events raised on -/// `CoreWebView2Frame`. +/// Receives `LaunchingExternalUriScheme` events raised on `CoreWebView2Frame`. [uuid(8d0a4bee-a888-50bc-8088-a71678fd3af3), object, pointer_default(unique)] interface ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler : IUnknown { @@ -275,32 +257,29 @@ interface ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler HRESULT Invoke( [in] ICoreWebView2Frame* sender, [in] ICoreWebView2LaunchingExternalUriSchemeEventArgs2* args); -} +}; -/// This is a continuation of the -/// `ICoreWebView2LaunchingExternalUriSchemeEventArgs` interface that -/// adds the `Handled` property. +/// Extends `ICoreWebView2LaunchingExternalUriSchemeEventArgs` with a `Handled` +/// property. [uuid(126db12c-f6dc-51b7-afa4-3eecb5304b9f), object, pointer_default(unique)] interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2 : ICoreWebView2LaunchingExternalUriSchemeEventArgs { - /// By default, both the `LaunchingExternalUriScheme` event handlers on - /// the `CoreWebView2Frame` and the `CoreWebView2` will be invoked, - /// with the `CoreWebView2Frame` event handlers invoked first. The host - /// may set this flag to `TRUE` within the `CoreWebView2Frame` event - /// handlers to prevent the remaining `CoreWebView2` event handlers - /// from being invoked. + /// By default, the `LaunchingExternalUriScheme` event is raised on both + /// `CoreWebView2Frame` and `CoreWebView2`, with frame-level handlers + /// invoked first. Set this property to `TRUE` within a `CoreWebView2Frame` + /// handler to prevent the event from being raised on `CoreWebView2`. /// - /// If a deferral is taken on the event args, then you must - /// synchronously set `Handled` to `TRUE` prior to taking your deferral - /// to prevent the `CoreWebView2`'s event handlers from being invoked. + /// If a deferral is taken, set this property to `TRUE` synchronously before + /// taking the deferral to prevent `CoreWebView2` handlers from being + /// invoked. [propget] HRESULT Handled([out, retval] BOOL* value); /// Sets the `Handled` property. [propput] HRESULT Handled([in] BOOL value); -} +}; ``` -## .NET/C# +## .NET C# ```c# namespace Microsoft.Web.WebView2.Core @@ -313,11 +292,12 @@ namespace Microsoft.Web.WebView2.Core "Microsoft.Web.WebView2.Core.ICoreWebView2LaunchingExternalUriSchemeEventArgs2")] { [doc_string( - "The host may set this flag to TRUE to prevent the " - "LaunchingExternalUriScheme event from firing on the " - "CoreWebView2 as well. By default, both the " - "LaunchingExternalUriScheme on the CoreWebView2Frame " - "and the CoreWebView2 will be raised.")] + "Set this property to TRUE to prevent the " + "LaunchingExternalUriScheme event from being raised on " + "CoreWebView2. By default, the event is raised on both " + "CoreWebView2Frame and CoreWebView2, with frame-level " + "handlers invoked first. If a deferral is taken, set this " + "property to TRUE synchronously before taking the deferral.")] Boolean Handled { get; set; }; } } @@ -330,15 +310,14 @@ namespace Microsoft.Web.WebView2.Core "Microsoft.Web.WebView2.Core.ICoreWebView2ExperimentalFrame10")] { [doc_string( - "LaunchingExternalUriScheme is raised when content in " - "this CoreWebView2Frame (or any of its descendant " - "iframes that don't have a closer tracked " - "CoreWebView2Frame ancestor) attempts to launch a URI " - "registered with the OS as an external scheme handler. " - "The CoreWebView2Frame's event handlers are invoked " - "before the CoreWebView2's event handlers; set Handled " - "to TRUE in the frame handler to suppress the " - "webview-level handlers.")] + "The LaunchingExternalUriScheme event is raised when content " + "in this CoreWebView2Frame, or in a descendant iframe that " + "does not have a closer tracked CoreWebView2Frame ancestor, " + "attempts to launch a URI registered with the OS as an " + "external scheme handler. Frame-level handlers are invoked " + "before CoreWebView2 handlers. Set Handled to TRUE in the " + "frame handler to prevent CoreWebView2 handlers from being " + "invoked.")] event Windows.Foundation.TypedEventHandler< CoreWebView2Frame, CoreWebView2LaunchingExternalUriSchemeEventArgs> @@ -353,49 +332,47 @@ namespace Microsoft.Web.WebView2.Core ## Alternative considered: `OriginalSourceFrameInfo` on the webview-level args -We considered surfacing the initiating frame on the existing webview-level -event via an `OriginalSourceFrameInfo` property, mirroring the pattern -shipped on +An alternative considered was surfacing the initiating frame on the existing +webview-level event through an `OriginalSourceFrameInfo` property, mirroring +the pattern shipped on [`ICoreWebView2NewWindowRequestedEventArgs3`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2newwindowrequestedeventargs3#get_originalsourceframeinfo). -That approach delivers the iframe identity to a single webview-level -handler. +That approach delivers the iframe identity to a single webview-level handler. -We chose the frame-level event instead because it: +The frame-level event was chosen instead because it: -* aligns with the per-frame ergonomic already shipped for +* aligns with the per-frame model already shipped for [`PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) and [`ScreenCaptureStarting`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame6#add_screencapturestarting); * allows independent handler registration per iframe, decoupling default webview-level policy from per-iframe rules; -* makes the iframe object directly available as the event `sender`, - removing the need for a `QueryInterface` on the args; and -* together with `Handled` lets a frame handler cleanly suppress the - webview handler — enabling allowlist / default-deny patterns where the - webview-level handler is the default policy and per-iframe handlers +* makes the iframe object directly available as the event `sender`, removing + the need for a `QueryInterface` on the args; and +* together with `Handled`, allows a frame-level handler to suppress the + webview-level handler, enabling allowlist and default-deny patterns where + the webview-level handler is the default policy and per-iframe handlers override it. -The two approaches are not mutually exclusive — `OriginalSourceFrameInfo` -on the args could be added later if a single-handler tier is preferred -for some scenarios. +The two approaches are not mutually exclusive. `OriginalSourceFrameInfo` on +the args could be added later if a single handler tier is preferred for some +scenarios. ## Nested iframes -The event surfaces on the closest tracked `CoreWebView2Frame` in the -initiating iframe's ancestor chain. This matches the routing convention -already used by -[`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested): -the host registers `FrameCreated` handlers (top-level iframes only by -default; or use +The event is raised on the closest tracked `CoreWebView2Frame` in the +initiating iframe's ancestor chain. This matches the routing used by +[`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested). +The host registers `FrameCreated` handlers (top-level iframes only by default, +or [`ICoreWebView2Frame7.add_FrameCreated`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame7#add_framecreated) -to track nested iframes), and `LaunchingExternalUriScheme` is delivered -to the closest tracked iframe up the chain. +to track nested iframes), and `LaunchingExternalUriScheme` is delivered to the +closest tracked iframe in the chain. -## Same-origin / cross-origin iframes +## Same-origin and cross-origin iframes -The event fires regardless of whether the iframe shares an origin with -its parent. Cross-origin iframes without a user gesture still follow -existing WebView2 behavior: the external-URI launch is blocked and the -event is not raised, matching the +The event is raised regardless of whether the iframe shares an origin with its +parent. Cross-origin iframes without a user gesture follow existing WebView2 +behavior: the external URI launch is blocked and the event is not raised, +matching the [`CoreWebView2.LaunchingExternalUriScheme`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2_18#add_launchingexternalurischeme) contract. From d18e72d5bc3ec4d5154de9841585dd9508012b03 Mon Sep 17 00:00:00 2001 From: Vipul Gupta Date: Tue, 16 Jun 2026 16:50:14 +0530 Subject: [PATCH 5/9] Adopt reviewer-suggested structure and restore Appendix --- specs/FrameLaunchingExternalUriScheme.md | 282 ++++++++++++----------- 1 file changed, 142 insertions(+), 140 deletions(-) diff --git a/specs/FrameLaunchingExternalUriScheme.md b/specs/FrameLaunchingExternalUriScheme.md index 361abdd42..95641f4f1 100644 --- a/specs/FrameLaunchingExternalUriScheme.md +++ b/specs/FrameLaunchingExternalUriScheme.md @@ -1,86 +1,101 @@ -# Background - -WebView2 raises the `LaunchingExternalUriScheme` event on `CoreWebView2` -when web content attempts to launch a URI scheme registered with the OS as -an external scheme handler (for example, `mailto:`, `tel:`, or a custom -protocol). The host can handle this event to suppress the default WebView2 -dialog, present its own consent UI, and set `Cancel` to control whether the -URI is launched. - -The event currently exposes `Uri`, `InitiatingOrigin`, `IsUserInitiated`, -and `Cancel`. It does not identify the originating iframe. Hosts that embed -multiple sub-applications as iframes within a single WebView2 — often served -from a shared origin — cannot reliably attribute a launch to the iframe that -triggered it, because: - -1. Multiple iframes can be served from the same origin, and the event - exposes no identifier beyond the origin. -2. The same iframe content may be presented in more than one host surface - (window or panel), and the host cannot route the consent prompt to the - correct surface. +# Frame-Level LaunchingExternalUriScheme + +## Background + +WebView2 raises `LaunchingExternalUriScheme` on `CoreWebView2` when web +content attempts to launch a URI scheme registered with the OS as an external +handler (for example, `mailto:`, `tel:`, or a custom protocol). The host can +intercept this event to suppress the default WebView2 dialog, present custom +consent UI, and set `Cancel` to control whether the URI is launched. + +The event currently exposes `Uri`, `InitiatingOrigin`, `IsUserInitiated`, and +`Cancel`. It does not identify the originating iframe. As a result, hosts that +embed multiple sub-applications in iframes within a single WebView2—often +served from a shared origin—cannot reliably attribute a launch to a specific +iframe. + +This limitation impacts several scenarios: + +1. Multiple iframes may share the same origin, and the event exposes no + identifier beyond the origin. +2. The same iframe content may be hosted in multiple surfaces (for example, + windows or panels), and the host cannot route consent UI to the correct + surface. 3. Sandboxed and `srcdoc` iframes have opaque origins. The API reports the parent origin, making the initiating iframe indistinguishable. -To enable iframe-level attribution, the `LaunchingExternalUriScheme` event -is also raised on `CoreWebView2Frame`. The host can register a handler per -iframe, and the iframe is the `sender` of the event, providing direct -attribution without an additional `QueryInterface` or name lookup. A new -`Handled` property on the event args allows a frame-level handler to suppress -the webview-level handlers for that launch. +To enable iframe-level attribution, `LaunchingExternalUriScheme` is also +raised on `CoreWebView2Frame`. This allows handlers to be registered per +iframe and provides direct attribution through the iframe instance as the +event sender. + +`CoreWebView2LaunchingExternalUriSchemeEventArgs` includes a `Handled` +property that allows a frame-level handler to prevent the event from being +raised on `CoreWebView2`. + +This design aligns with the per-frame event model used by: -This design follows the per-frame event model used by -[`ICoreWebView2Frame3::add_PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) -and -[`ICoreWebView2PermissionRequestedEventArgs2::Handled`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2permissionrequestedeventargs2#put_handled). -A related attribution mechanism, -[`OriginalSourceFrameInfo` on `ICoreWebView2NewWindowRequestedEventArgs3`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2newwindowrequestedeventargs3#get_originalsourceframeinfo), -addresses the same problem for the `NewWindowRequested` event. The trade-offs -between the two approaches are described in the Appendix. +- [`ICoreWebView2Frame3::add_PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) +- [`ICoreWebView2PermissionRequestedEventArgs2::Handled`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2permissionrequestedeventargs2#put_handled) +A related attribution mechanism exists in +[`ICoreWebView2NewWindowRequestedEventArgs3::OriginalSourceFrameInfo`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2newwindowrequestedeventargs3#get_originalsourceframeinfo), +which addresses the same attribution problem for the `NewWindowRequested` +event. The differences between these approaches are described in the Appendix. -# Description +--- -The `LaunchingExternalUriScheme` event is raised on `CoreWebView2Frame` when -content in that frame, or in a descendant iframe that does not have a closer -tracked `CoreWebView2Frame` ancestor, attempts to launch an external URI -scheme. The `sender` of the event is the `CoreWebView2Frame`, so the host can -attribute the launch to a specific iframe. +## Description -`CoreWebView2LaunchingExternalUriSchemeEventArgs` exposes a new `Handled` +`CoreWebView2Frame` exposes a `LaunchingExternalUriScheme` event. + +The event is raised when content in a `CoreWebView2Frame`, or in a descendant +iframe that does not have a closer tracked `CoreWebView2Frame` ancestor, +attempts to launch an external URI scheme. The event `sender` is the +`CoreWebView2Frame`, enabling direct attribution to the initiating iframe. + +`CoreWebView2LaunchingExternalUriSchemeEventArgs` includes a `Handled` property. By default, the event is raised on both `CoreWebView2Frame` and `CoreWebView2`. Frame-level handlers are invoked before webview-level -handlers. If `Handled` is set to `TRUE` in a frame-level handler, the event -is not raised on `CoreWebView2`, and its handlers are not invoked. This -preserves backward compatibility for hosts that handle the event only on -`CoreWebView2`. - -For nested iframes, the event is raised on the closest tracked -`CoreWebView2Frame` in the initiating iframe's ancestor chain, matching the -routing used by -[`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested). +handlers. If `Handled` is set to `TRUE` in a frame-level handler, the event is +not raised on `CoreWebView2`, and its handlers are not invoked. -The event args are shared between the two handler tiers. Properties set by -the frame-level handler, including `Cancel` and `Handled`, are visible to -webview-level handlers. A `Deferral` taken in either tier blocks the launch -until the deferral is completed. +Event args are shared between handler tiers. Properties set in the frame-level +handler, including `Cancel` and `Handled`, are visible to webview-level +handlers. A `Deferral` taken in either handler tier blocks the launch until +the deferral is completed. + +If a deferral is taken, `Handled` must be set synchronously before taking the +deferral to prevent `CoreWebView2`-level handlers from being invoked. `Cancel` controls whether the URI is launched. `Handled` controls whether -webview-level handlers are invoked. The two are independent. +webview-level handlers are invoked. +### Nested iframes -# Examples +The event is raised on the closest tracked `CoreWebView2Frame` in the +initiating iframe's ancestor chain. This matches the routing behavior of +[`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested). -## Registering a per-iframe LaunchingExternalUriScheme handler +### Same-origin and cross-origin iframes -A host that embeds multiple sub-applications in iframes can register a handler -per iframe to attribute external URI launches to a specific sub-application -and present a consent prompt that names the correct one. Setting -`Handled = TRUE` in the frame-level handler prevents the webview-level handler -from being invoked for the same event. +The event is raised regardless of origin. For cross-origin iframes without a +user gesture, existing WebView2 behavior applies: the launch is blocked and +the event is not raised, consistent with +[`CoreWebView2.LaunchingExternalUriScheme`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2_18#add_launchingexternalurischeme). -### Win32 C++ +## Examples + +### Registering a per-iframe handler + +A host embedding multiple sub-applications in iframes can register a handler +per iframe to attribute external URI launches and present iframe-specific +consent UI. Setting `Handled = TRUE` prevents the webview-level handler from +being invoked. + +### C++ ```cpp AppWindow* m_appWindow; @@ -120,11 +135,11 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() args) -> HRESULT { // Avoid reentrancy by scheduling the dialog - // asynchronously, outside this event handler. - // Because a deferral is taken, set `Handled` to - // TRUE synchronously before taking the deferral - // so that the `CoreWebView2`-level handlers are - // not invoked. + // asynchronously outside the event handler. + // Because a deferral is taken, set `Handled` + // to TRUE synchronously before taking the + // deferral. This prevents `CoreWebView2`-level + // handlers from being invoked. CHECK_FAILURE(args->put_Handled(TRUE)); wil::com_ptr deferral; @@ -158,10 +173,12 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() response == IDYES ? FALSE : TRUE)); CHECK_FAILURE(deferral->Complete()); }); + return S_OK; }) .Get(), &m_frameLaunchingExternalUriSchemeToken)); + return S_OK; }) .Get(), @@ -169,7 +186,7 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() } ``` -### .NET C# +### C# ```c# private WebView2 m_webview; @@ -182,14 +199,15 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() async (frameSender, args) => { // Because asynchronous work is awaited below, set `Handled` - // synchronously before taking the deferral so that the - // `CoreWebView2`-level handlers are not invoked. + // synchronously before taking the deferral. This prevents + // `CoreWebView2`-level handlers from being invoked. args.Handled = true; CoreWebView2Deferral deferral = args.GetDeferral(); using (deferral) { - string message = $"The \"{frameSender.Name}\" iframe is " + + string message = + $"The \"{frameSender.Name}\" iframe is " + $"attempting to launch an external URI scheme for " + $"{args.Uri}.\n\nAllow this action?"; @@ -205,10 +223,9 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() } ``` - # API Details -## Win32 C++ +## Win32 (C++) ```cpp interface ICoreWebView2ExperimentalFrame10; @@ -216,62 +233,67 @@ interface ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler; interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2; /// Extends the `ICoreWebView2Frame` interface to expose the -/// `LaunchingExternalUriScheme` event at the iframe level. Host applications -/// can subscribe per iframe to attribute external URI scheme launches to a -/// specific iframe, even when multiple frames share the same origin. +/// `LaunchingExternalUriScheme` event at the iframe level. +/// Host applications can subscribe on a per-frame basis to attribute +/// external URI scheme launches to a specific iframe, even when multiple +/// frames share the same origin. [uuid(0e3c31b7-bbca-5216-a2d1-40e7a211f0ab), object, pointer_default(unique)] interface ICoreWebView2ExperimentalFrame10 : IUnknown { - /// Adds an event handler for the `LaunchingExternalUriScheme` event. The - /// event is raised when content in this `CoreWebView2Frame`, or in a + /// Adds an event handler for the `LaunchingExternalUriScheme` event. + /// The event is raised when content in this `CoreWebView2Frame`, or in a /// descendant iframe that does not have a closer tracked - /// `CoreWebView2Frame` ancestor, attempts to launch a URI registered with - /// the OS as an external scheme handler. + /// `CoreWebView2Frame` ancestor, attempts to launch a URI registered + /// with the OS as an external scheme handler. /// - /// This event corresponds to `CoreWebView2.LaunchingExternalUriScheme`. For - /// an iframe-initiated launch, `CoreWebView2Frame` handlers are invoked - /// before `CoreWebView2` handlers. If the `Handled` property on + /// This event corresponds to `CoreWebView2.LaunchingExternalUriScheme`. + /// For iframe-initiated launches, `CoreWebView2Frame` handlers are + /// invoked before `CoreWebView2` handlers. If the `Handled` property on /// `ICoreWebView2LaunchingExternalUriSchemeEventArgs2` is set to `TRUE` /// within a `CoreWebView2Frame` handler, the event is not raised on /// `CoreWebView2`, and its handlers are not invoked. /// /// If a deferral is not taken, the external URI scheme launch is blocked - /// until the handler returns. If a deferral is taken, the launch is blocked - /// until the deferral is completed. To prevent `CoreWebView2` handlers from - /// being invoked, set `Handled` synchronously before taking a deferral. + /// until the handler returns. If a deferral is taken, the launch remains + /// blocked until the deferral is completed. + /// + /// To prevent `CoreWebView2` handlers from being invoked, `Handled` must + /// be set synchronously before taking a deferral. HRESULT add_LaunchingExternalUriScheme( [in] ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler* eventHandler, [out] EventRegistrationToken* token); - /// Removes an event handler previously added with + /// Removes an event handler previously added by /// `add_LaunchingExternalUriScheme`. HRESULT remove_LaunchingExternalUriScheme( [in] EventRegistrationToken token); }; -/// Receives `LaunchingExternalUriScheme` events raised on `CoreWebView2Frame`. +/// Receives `LaunchingExternalUriScheme` events raised on +/// `CoreWebView2Frame`. [uuid(8d0a4bee-a888-50bc-8088-a71678fd3af3), object, pointer_default(unique)] interface ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler : IUnknown { - /// Provides the event args for the corresponding event. + /// Invoked when the corresponding event is raised. HRESULT Invoke( [in] ICoreWebView2Frame* sender, [in] ICoreWebView2LaunchingExternalUriSchemeEventArgs2* args); }; -/// Extends `ICoreWebView2LaunchingExternalUriSchemeEventArgs` with a `Handled` -/// property. +/// Extends `ICoreWebView2LaunchingExternalUriSchemeEventArgs` with a +/// `Handled` property. [uuid(126db12c-f6dc-51b7-afa4-3eecb5304b9f), object, pointer_default(unique)] interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2 : ICoreWebView2LaunchingExternalUriSchemeEventArgs { /// By default, the `LaunchingExternalUriScheme` event is raised on both /// `CoreWebView2Frame` and `CoreWebView2`, with frame-level handlers - /// invoked first. Set this property to `TRUE` within a `CoreWebView2Frame` - /// handler to prevent the event from being raised on `CoreWebView2`. + /// invoked first. Set this property to `TRUE` within a + /// `CoreWebView2Frame` handler to prevent the event from being raised on + /// `CoreWebView2`. /// - /// If a deferral is taken, set this property to `TRUE` synchronously before - /// taking the deferral to prevent `CoreWebView2` handlers from being - /// invoked. + /// If a deferral is taken, this property must be set to `TRUE` + /// synchronously before taking the deferral to prevent + /// `CoreWebView2` handlers from being invoked. [propget] HRESULT Handled([out, retval] BOOL* value); /// Sets the `Handled` property. @@ -279,7 +301,7 @@ interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2 }; ``` -## .NET C# +## .Net/C# ```c# namespace Microsoft.Web.WebView2.Core @@ -296,8 +318,9 @@ namespace Microsoft.Web.WebView2.Core "LaunchingExternalUriScheme event from being raised on " "CoreWebView2. By default, the event is raised on both " "CoreWebView2Frame and CoreWebView2, with frame-level " - "handlers invoked first. If a deferral is taken, set this " - "property to TRUE synchronously before taking the deferral.")] + "handlers invoked first. If a deferral is taken, this " + "property must be set to TRUE synchronously before " + "taking the deferral.")] Boolean Handled { get; set; }; } } @@ -310,14 +333,14 @@ namespace Microsoft.Web.WebView2.Core "Microsoft.Web.WebView2.Core.ICoreWebView2ExperimentalFrame10")] { [doc_string( - "The LaunchingExternalUriScheme event is raised when content " - "in this CoreWebView2Frame, or in a descendant iframe that " - "does not have a closer tracked CoreWebView2Frame ancestor, " - "attempts to launch a URI registered with the OS as an " - "external scheme handler. Frame-level handlers are invoked " - "before CoreWebView2 handlers. Set Handled to TRUE in the " - "frame handler to prevent CoreWebView2 handlers from being " - "invoked.")] + "The LaunchingExternalUriScheme event is raised when " + "content in this CoreWebView2Frame, or in a descendant " + "iframe that does not have a closer tracked " + "CoreWebView2Frame ancestor, attempts to launch a URI " + "registered with the OS as an external scheme handler. " + "Frame-level handlers are invoked before CoreWebView2 " + "handlers. Set Handled to TRUE in the frame handler to " + "prevent CoreWebView2 handlers from being invoked.")] event Windows.Foundation.TypedEventHandler< CoreWebView2Frame, CoreWebView2LaunchingExternalUriSchemeEventArgs> @@ -327,7 +350,6 @@ namespace Microsoft.Web.WebView2.Core } ``` - # Appendix ## Alternative considered: `OriginalSourceFrameInfo` on the webview-level args @@ -340,39 +362,19 @@ That approach delivers the iframe identity to a single webview-level handler. The frame-level event was chosen instead because it: -* aligns with the per-frame model already shipped for +- aligns with the per-frame model already shipped for [`PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) and [`ScreenCaptureStarting`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame6#add_screencapturestarting); -* allows independent handler registration per iframe, decoupling default +- allows independent handler registration per iframe, decoupling default webview-level policy from per-iframe rules; -* makes the iframe object directly available as the event `sender`, removing - the need for a `QueryInterface` on the args; and -* together with `Handled`, allows a frame-level handler to suppress the - webview-level handler, enabling allowlist and default-deny patterns where - the webview-level handler is the default policy and per-iframe handlers - override it. - -The two approaches are not mutually exclusive. `OriginalSourceFrameInfo` on -the args could be added later if a single handler tier is preferred for some +- exposes the iframe object directly as the event `sender`, removing the need + for a `QueryInterface` on the args; and +- together with `Handled`, allows a frame-level handler to suppress the + webview-level handler, enabling allowlist and default-deny patterns where the + webview-level handler is the default policy and per-iframe handlers override + it. + +The two approaches are not mutually exclusive. `OriginalSourceFrameInfo` on the +args could be added later if a single handler tier is preferred for some scenarios. - -## Nested iframes - -The event is raised on the closest tracked `CoreWebView2Frame` in the -initiating iframe's ancestor chain. This matches the routing used by -[`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested). -The host registers `FrameCreated` handlers (top-level iframes only by default, -or -[`ICoreWebView2Frame7.add_FrameCreated`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame7#add_framecreated) -to track nested iframes), and `LaunchingExternalUriScheme` is delivered to the -closest tracked iframe in the chain. - -## Same-origin and cross-origin iframes - -The event is raised regardless of whether the iframe shares an origin with its -parent. Cross-origin iframes without a user gesture follow existing WebView2 -behavior: the external URI launch is blocked and the event is not raised, -matching the -[`CoreWebView2.LaunchingExternalUriScheme`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2_18#add_launchingexternalurischeme) -contract. From ae8c697a240ff981e78629a50445f59fa2297132 Mon Sep 17 00:00:00 2001 From: Vipul Gupta Date: Tue, 23 Jun 2026 12:37:37 +0530 Subject: [PATCH 6/9] Polish event-routing sentence for clarity --- specs/FrameLaunchingExternalUriScheme.md | 28 ++++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/specs/FrameLaunchingExternalUriScheme.md b/specs/FrameLaunchingExternalUriScheme.md index 95641f4f1..bf706ca97 100644 --- a/specs/FrameLaunchingExternalUriScheme.md +++ b/specs/FrameLaunchingExternalUriScheme.md @@ -49,10 +49,12 @@ event. The differences between these approaches are described in the Appendix. `CoreWebView2Frame` exposes a `LaunchingExternalUriScheme` event. -The event is raised when content in a `CoreWebView2Frame`, or in a descendant -iframe that does not have a closer tracked `CoreWebView2Frame` ancestor, -attempts to launch an external URI scheme. The event `sender` is the -`CoreWebView2Frame`, enabling direct attribution to the initiating iframe. +The event is raised on a `CoreWebView2Frame` when content within it, or +within one of its descendant iframes, attempts to launch an external URI +scheme. When the launch originates from a nested iframe, the event is raised +on the closest tracked `CoreWebView2Frame` ancestor. The event `sender` is +that `CoreWebView2Frame`, enabling direct attribution to the initiating +iframe. `CoreWebView2LaunchingExternalUriSchemeEventArgs` includes a `Handled` property. @@ -240,10 +242,11 @@ interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2; [uuid(0e3c31b7-bbca-5216-a2d1-40e7a211f0ab), object, pointer_default(unique)] interface ICoreWebView2ExperimentalFrame10 : IUnknown { /// Adds an event handler for the `LaunchingExternalUriScheme` event. - /// The event is raised when content in this `CoreWebView2Frame`, or in a - /// descendant iframe that does not have a closer tracked - /// `CoreWebView2Frame` ancestor, attempts to launch a URI registered - /// with the OS as an external scheme handler. + /// The event is raised when content in this `CoreWebView2Frame`, or in one + /// of its descendant iframes, attempts to launch a URI registered with the + /// OS as an external scheme handler. When the launch originates from a + /// nested iframe, the event is raised on the closest tracked + /// `CoreWebView2Frame` ancestor. /// /// This event corresponds to `CoreWebView2.LaunchingExternalUriScheme`. /// For iframe-initiated launches, `CoreWebView2Frame` handlers are @@ -334,10 +337,11 @@ namespace Microsoft.Web.WebView2.Core { [doc_string( "The LaunchingExternalUriScheme event is raised when " - "content in this CoreWebView2Frame, or in a descendant " - "iframe that does not have a closer tracked " - "CoreWebView2Frame ancestor, attempts to launch a URI " - "registered with the OS as an external scheme handler. " + "content in this CoreWebView2Frame, or in one of its " + "descendant iframes, attempts to launch a URI registered " + "with the OS as an external scheme handler. When the launch " + "originates from a nested iframe, the event is raised on the " + "closest tracked CoreWebView2Frame ancestor. " "Frame-level handlers are invoked before CoreWebView2 " "handlers. Set Handled to TRUE in the frame handler to " "prevent CoreWebView2 handlers from being invoked.")] From 4bad747794b85ccbabca5574bb7318898db3e40b Mon Sep 17 00:00:00 2001 From: Vipul Gupta Date: Tue, 23 Jun 2026 13:50:55 +0530 Subject: [PATCH 7/9] Simplify Background/Description, remove Appendix, add Related APIs --- specs/FrameLaunchingExternalUriScheme.md | 139 ++++++++--------------- 1 file changed, 45 insertions(+), 94 deletions(-) diff --git a/specs/FrameLaunchingExternalUriScheme.md b/specs/FrameLaunchingExternalUriScheme.md index bf706ca97..8e075a277 100644 --- a/specs/FrameLaunchingExternalUriScheme.md +++ b/specs/FrameLaunchingExternalUriScheme.md @@ -2,90 +2,64 @@ ## Background -WebView2 raises `LaunchingExternalUriScheme` on `CoreWebView2` when web -content attempts to launch a URI scheme registered with the OS as an external -handler (for example, `mailto:`, `tel:`, or a custom protocol). The host can -intercept this event to suppress the default WebView2 dialog, present custom -consent UI, and set `Cancel` to control whether the URI is launched. - -The event currently exposes `Uri`, `InitiatingOrigin`, `IsUserInitiated`, and -`Cancel`. It does not identify the originating iframe. As a result, hosts that -embed multiple sub-applications in iframes within a single WebView2—often -served from a shared origin—cannot reliably attribute a launch to a specific -iframe. - -This limitation impacts several scenarios: - -1. Multiple iframes may share the same origin, and the event exposes no - identifier beyond the origin. -2. The same iframe content may be hosted in multiple surfaces (for example, - windows or panels), and the host cannot route consent UI to the correct - surface. -3. Sandboxed and `srcdoc` iframes have opaque origins. The API reports the - parent origin, making the initiating iframe indistinguishable. - -To enable iframe-level attribution, `LaunchingExternalUriScheme` is also -raised on `CoreWebView2Frame`. This allows handlers to be registered per -iframe and provides direct attribution through the iframe instance as the -event sender. - -`CoreWebView2LaunchingExternalUriSchemeEventArgs` includes a `Handled` -property that allows a frame-level handler to prevent the event from being -raised on `CoreWebView2`. - -This design aligns with the per-frame event model used by: - -- [`ICoreWebView2Frame3::add_PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) -- [`ICoreWebView2PermissionRequestedEventArgs2::Handled`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2permissionrequestedeventargs2#put_handled) +`CoreWebView2.LaunchingExternalUriScheme` is raised when web content attempts +to launch a URI scheme handled by an external application (for example, +`mailto:`, `tel:`, or a custom protocol). Hosts can handle this event to +suppress the default WebView2 dialog, provide custom consent UI, and set +`Cancel` to control whether the URI is launched. -A related attribution mechanism exists in -[`ICoreWebView2NewWindowRequestedEventArgs3::OriginalSourceFrameInfo`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2newwindowrequestedeventargs3#get_originalsourceframeinfo), -which addresses the same attribution problem for the `NewWindowRequested` -event. The differences between these approaches are described in the Appendix. +The event arguments expose: ---- +- `Uri` +- `InitiatingOrigin` +- `IsUserInitiated` +- `Cancel` -## Description +This event does not identify the originating iframe. When multiple +sub-applications are hosted within iframes, this can prevent reliable +attribution of the request to a specific iframe, including: + +- When multiple iframes share the same origin. +- When the same content is hosted in multiple surfaces (for example, windows + or panels). +- When sandboxed or `srcdoc` iframes report an opaque or inherited origin. -`CoreWebView2Frame` exposes a `LaunchingExternalUriScheme` event. +## Description -The event is raised on a `CoreWebView2Frame` when content within it, or -within one of its descendant iframes, attempts to launch an external URI -scheme. When the launch originates from a nested iframe, the event is raised -on the closest tracked `CoreWebView2Frame` ancestor. The event `sender` is -that `CoreWebView2Frame`, enabling direct attribution to the initiating -iframe. +`LaunchingExternalUriScheme` is also raised on `CoreWebView2Frame`. -`CoreWebView2LaunchingExternalUriSchemeEventArgs` includes a `Handled` -property. +The event is raised when content in a frame, or in an iframe nested within it, +attempts to launch an external URI scheme. When the launch originates from a +nested iframe, the event is raised on the closest tracked `CoreWebView2Frame` +ancestor. The event sender is that `CoreWebView2Frame`, enabling attribution to +the initiating frame. -By default, the event is raised on both `CoreWebView2Frame` and -`CoreWebView2`. Frame-level handlers are invoked before webview-level -handlers. If `Handled` is set to `TRUE` in a frame-level handler, the event is -not raised on `CoreWebView2`, and its handlers are not invoked. +`CoreWebView2LaunchingExternalUriSchemeEventArgs` adds a `Handled` property. -Event args are shared between handler tiers. Properties set in the frame-level -handler, including `Cancel` and `Handled`, are visible to webview-level -handlers. A `Deferral` taken in either handler tier blocks the launch until -the deferral is completed. +By default, the event is raised on both `CoreWebView2Frame` and `CoreWebView2`. +Frame-level handlers are invoked before WebView-level handlers. If a +frame-level handler sets `Handled` to `TRUE`, the event is not raised on +`CoreWebView2`, and its handlers are not invoked. -If a deferral is taken, `Handled` must be set synchronously before taking the -deferral to prevent `CoreWebView2`-level handlers from being invoked. +The event arguments are shared between handler tiers. Properties set in a +frame-level handler, including `Cancel` and `Handled`, are visible to +WebView-level handlers. A `Deferral` taken in either handler tier delays the +URI launch until the deferral is completed. To suppress the WebView-level +handlers, set `Handled` before taking the deferral. `Cancel` controls whether the URI is launched. `Handled` controls whether -webview-level handlers are invoked. +WebView-level handlers are invoked. ### Nested iframes The event is raised on the closest tracked `CoreWebView2Frame` in the -initiating iframe's ancestor chain. This matches the routing behavior of +initiating iframe's ancestor chain. This behavior is consistent with [`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested). ### Same-origin and cross-origin iframes -The event is raised regardless of origin. For cross-origin iframes without a -user gesture, existing WebView2 behavior applies: the launch is blocked and -the event is not raised, consistent with +The event is raised regardless of origin. For cross-origin iframes without user +activation, the launch is blocked and the event is not raised, consistent with [`CoreWebView2.LaunchingExternalUriScheme`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2_18#add_launchingexternalurischeme). ## Examples @@ -94,7 +68,7 @@ the event is not raised, consistent with A host embedding multiple sub-applications in iframes can register a handler per iframe to attribute external URI launches and present iframe-specific -consent UI. Setting `Handled = TRUE` prevents the webview-level handler from +consent UI. Setting `Handled = TRUE` prevents the WebView-level handlers from being invoked. ### C++ @@ -354,31 +328,8 @@ namespace Microsoft.Web.WebView2.Core } ``` -# Appendix - -## Alternative considered: `OriginalSourceFrameInfo` on the webview-level args - -An alternative considered was surfacing the initiating frame on the existing -webview-level event through an `OriginalSourceFrameInfo` property, mirroring -the pattern shipped on -[`ICoreWebView2NewWindowRequestedEventArgs3`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2newwindowrequestedeventargs3#get_originalsourceframeinfo). -That approach delivers the iframe identity to a single webview-level handler. - -The frame-level event was chosen instead because it: - -- aligns with the per-frame model already shipped for - [`PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) - and - [`ScreenCaptureStarting`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame6#add_screencapturestarting); -- allows independent handler registration per iframe, decoupling default - webview-level policy from per-iframe rules; -- exposes the iframe object directly as the event `sender`, removing the need - for a `QueryInterface` on the args; and -- together with `Handled`, allows a frame-level handler to suppress the - webview-level handler, enabling allowlist and default-deny patterns where the - webview-level handler is the default policy and per-iframe handlers override - it. - -The two approaches are not mutually exclusive. `OriginalSourceFrameInfo` on the -args could be added later if a single handler tier is preferred for some -scenarios. +# Related APIs + +- [`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) +- [`ICoreWebView2PermissionRequestedEventArgs2::Handled`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2permissionrequestedeventargs2#put_handled) +- [`ICoreWebView2NewWindowRequestedEventArgs3::OriginalSourceFrameInfo`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2newwindowrequestedeventargs3#get_originalsourceframeinfo) From 13e9f7db9edce5a3f2fbb5cc3f72958edfbe0ffc Mon Sep 17 00:00:00 2001 From: Vipul Gupta Date: Tue, 23 Jun 2026 14:50:07 +0530 Subject: [PATCH 8/9] Remove non-standard Related APIs section --- specs/FrameLaunchingExternalUriScheme.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/specs/FrameLaunchingExternalUriScheme.md b/specs/FrameLaunchingExternalUriScheme.md index 8e075a277..a33c68375 100644 --- a/specs/FrameLaunchingExternalUriScheme.md +++ b/specs/FrameLaunchingExternalUriScheme.md @@ -327,9 +327,3 @@ namespace Microsoft.Web.WebView2.Core } } ``` - -# Related APIs - -- [`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested) -- [`ICoreWebView2PermissionRequestedEventArgs2::Handled`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2permissionrequestedeventargs2#put_handled) -- [`ICoreWebView2NewWindowRequestedEventArgs3::OriginalSourceFrameInfo`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2newwindowrequestedeventargs3#get_originalsourceframeinfo) From e3fdb293b702c1f29f70a5f9b0df64f9cf8d09f8 Mon Sep 17 00:00:00 2001 From: Vipul Gupta Date: Fri, 26 Jun 2026 18:47:56 +0530 Subject: [PATCH 9/9] Use stable interface names, staging UUIDs, and document nested-frame event bubbling --- specs/FrameLaunchingExternalUriScheme.md | 65 ++++++++++++++---------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/specs/FrameLaunchingExternalUriScheme.md b/specs/FrameLaunchingExternalUriScheme.md index a33c68375..87cdb299d 100644 --- a/specs/FrameLaunchingExternalUriScheme.md +++ b/specs/FrameLaunchingExternalUriScheme.md @@ -30,11 +30,13 @@ attribution of the request to a specific iframe, including: The event is raised when content in a frame, or in an iframe nested within it, attempts to launch an external URI scheme. When the launch originates from a -nested iframe, the event is raised on the closest tracked `CoreWebView2Frame` -ancestor. The event sender is that `CoreWebView2Frame`, enabling attribution to -the initiating frame. +nested iframe, the event bubbles through each tracked `CoreWebView2Frame` in the +initiating iframe's ancestor chain, starting with the closest (innermost) +tracked frame and proceeding outward toward the top-level frame. The event +sender for each invocation is the `CoreWebView2Frame` receiving the event, +enabling attribution to the initiating frame. -`CoreWebView2LaunchingExternalUriSchemeEventArgs` adds a `Handled` property. +The `LaunchingExternalUriScheme` event arguments add a `Handled` property. By default, the event is raised on both `CoreWebView2Frame` and `CoreWebView2`. Frame-level handlers are invoked before WebView-level handlers. If a @@ -52,8 +54,12 @@ WebView-level handlers are invoked. ### Nested iframes -The event is raised on the closest tracked `CoreWebView2Frame` in the -initiating iframe's ancestor chain. This behavior is consistent with +When the launch originates from a nested iframe, the event bubbles outward +through the tracked `CoreWebView2Frame` ancestors. It is raised first on the +closest (innermost) tracked frame, then on each tracked ancestor frame in turn, +and finally on `CoreWebView2`. If a handler at any tier sets `Handled` to +`TRUE`, the event is not raised on the remaining ancestor frames or on +`CoreWebView2`. This behavior is consistent with [`CoreWebView2Frame.PermissionRequested`](https://learn.microsoft.com/microsoft-edge/webview2/reference/win32/icorewebview2frame3#add_permissionrequested). ### Same-origin and cross-origin iframes @@ -95,16 +101,16 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() wil::com_ptr webviewFrame; CHECK_FAILURE(args->get_Frame(&webviewFrame)); - auto frame10 = webviewFrame - .try_query(); - if (!frame10) + auto frame9 = webviewFrame + .try_query(); + if (!frame9) { return S_OK; } - CHECK_FAILURE(frame10->add_LaunchingExternalUriScheme( + CHECK_FAILURE(frame9->add_LaunchingExternalUriScheme( Callback< - ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler>( + ICoreWebView2FrameLaunchingExternalUriSchemeEventHandler>( [this]( ICoreWebView2Frame* frameSender, ICoreWebView2LaunchingExternalUriSchemeEventArgs2* @@ -204,8 +210,8 @@ void RegisterFrameLaunchingExternalUriSchemeHandler() ## Win32 (C++) ```cpp -interface ICoreWebView2ExperimentalFrame10; -interface ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler; +interface ICoreWebView2Frame9; +interface ICoreWebView2FrameLaunchingExternalUriSchemeEventHandler; interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2; /// Extends the `ICoreWebView2Frame` interface to expose the @@ -213,21 +219,23 @@ interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2; /// Host applications can subscribe on a per-frame basis to attribute /// external URI scheme launches to a specific iframe, even when multiple /// frames share the same origin. -[uuid(0e3c31b7-bbca-5216-a2d1-40e7a211f0ab), object, pointer_default(unique)] -interface ICoreWebView2ExperimentalFrame10 : IUnknown { +[uuid(42ba2542-3391-59b3-9099-5954b6b44af9), object, pointer_default(unique)] +interface ICoreWebView2Frame9 : IUnknown { /// Adds an event handler for the `LaunchingExternalUriScheme` event. /// The event is raised when content in this `CoreWebView2Frame`, or in one /// of its descendant iframes, attempts to launch a URI registered with the /// OS as an external scheme handler. When the launch originates from a - /// nested iframe, the event is raised on the closest tracked - /// `CoreWebView2Frame` ancestor. + /// nested iframe, the event bubbles outward through the tracked + /// `CoreWebView2Frame` ancestors, starting with the closest (innermost) + /// tracked frame and proceeding outward toward the top-level frame. /// /// This event corresponds to `CoreWebView2.LaunchingExternalUriScheme`. /// For iframe-initiated launches, `CoreWebView2Frame` handlers are /// invoked before `CoreWebView2` handlers. If the `Handled` property on /// `ICoreWebView2LaunchingExternalUriSchemeEventArgs2` is set to `TRUE` - /// within a `CoreWebView2Frame` handler, the event is not raised on - /// `CoreWebView2`, and its handlers are not invoked. + /// within a `CoreWebView2Frame` handler, the event is not raised on the + /// remaining ancestor frames or on `CoreWebView2`, and their handlers are + /// not invoked. /// /// If a deferral is not taken, the external URI scheme launch is blocked /// until the handler returns. If a deferral is taken, the launch remains @@ -236,7 +244,7 @@ interface ICoreWebView2ExperimentalFrame10 : IUnknown { /// To prevent `CoreWebView2` handlers from being invoked, `Handled` must /// be set synchronously before taking a deferral. HRESULT add_LaunchingExternalUriScheme( - [in] ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler* + [in] ICoreWebView2FrameLaunchingExternalUriSchemeEventHandler* eventHandler, [out] EventRegistrationToken* token); @@ -248,8 +256,8 @@ interface ICoreWebView2ExperimentalFrame10 : IUnknown { /// Receives `LaunchingExternalUriScheme` events raised on /// `CoreWebView2Frame`. -[uuid(8d0a4bee-a888-50bc-8088-a71678fd3af3), object, pointer_default(unique)] -interface ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler +[uuid(9521c767-9916-5a5e-897c-7bfccebe4720), object, pointer_default(unique)] +interface ICoreWebView2FrameLaunchingExternalUriSchemeEventHandler : IUnknown { /// Invoked when the corresponding event is raised. HRESULT Invoke( @@ -259,7 +267,7 @@ interface ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler /// Extends `ICoreWebView2LaunchingExternalUriSchemeEventArgs` with a /// `Handled` property. -[uuid(126db12c-f6dc-51b7-afa4-3eecb5304b9f), object, pointer_default(unique)] +[uuid(add51d52-f724-5ab6-b59a-fc032ee90416), object, pointer_default(unique)] interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2 : ICoreWebView2LaunchingExternalUriSchemeEventArgs { /// By default, the `LaunchingExternalUriScheme` event is raised on both @@ -307,18 +315,21 @@ namespace Microsoft.Web.WebView2.Core // ... [interface_name( - "Microsoft.Web.WebView2.Core.ICoreWebView2ExperimentalFrame10")] + "Microsoft.Web.WebView2.Core.ICoreWebView2Frame9")] { [doc_string( "The LaunchingExternalUriScheme event is raised when " "content in this CoreWebView2Frame, or in one of its " "descendant iframes, attempts to launch a URI registered " "with the OS as an external scheme handler. When the launch " - "originates from a nested iframe, the event is raised on the " - "closest tracked CoreWebView2Frame ancestor. " + "originates from a nested iframe, the event bubbles outward " + "through the tracked CoreWebView2Frame ancestors, starting " + "with the closest (innermost) tracked frame and proceeding " + "outward toward the top-level frame. " "Frame-level handlers are invoked before CoreWebView2 " "handlers. Set Handled to TRUE in the frame handler to " - "prevent CoreWebView2 handlers from being invoked.")] + "prevent the remaining ancestor frame and CoreWebView2 " + "handlers from being invoked.")] event Windows.Foundation.TypedEventHandler< CoreWebView2Frame, CoreWebView2LaunchingExternalUriSchemeEventArgs>