API Review: Frame-Level LaunchingExternalUriScheme#5621
Conversation
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.
|
@microsoft-github-policy-service agree company="Microsoft" |
| @@ -0,0 +1,401 @@ | |||
| Frame-Level LaunchingExternalUriScheme | |||
There was a problem hiding this comment.
Requires rewriting to align with Windows Api documentation style. Voice, grammar, tone etc needs correction. Change to something like ...
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:
- Multiple iframes may share the same origin, and the event exposes no
identifier beyond the origin. - 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. - Sandboxed and
srcdociframes 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_PermissionRequestedICoreWebView2PermissionRequestedEventArgs2::Handled
A related attribution mechanism exists in
ICoreWebView2NewWindowRequestedEventArgs3::OriginalSourceFrameInfo.
The differences between these approaches are described in the Appendix.
Description
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.
For nested iframes, the event is raised on the closest tracked
CoreWebView2Frame in the ancestor chain. This matches the routing
behavior of CoreWebView2Frame.PermissionRequested.
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.
Nested iframes
The event is raised on the closest tracked CoreWebView2Frame in the
ancestor chain. This matches existing routing behavior for frame-level
events.
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
CoreWebView2.LaunchingExternalUriScheme.
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++
AppWindow* m_appWindow;
wil::com_ptr<ICoreWebView2> m_webview;
EventRegistrationToken m_frameCreatedToken = {};
EventRegistrationToken m_frameLaunchingExternalUriSchemeToken = {};
void RegisterFrameLaunchingExternalUriSchemeHandler()
{
auto webview4 = m_webview.try_query<ICoreWebView2_4>();
if (!webview4)
{
return;
}
CHECK_FAILURE(webview4->add_FrameCreated(
Callback<ICoreWebView2FrameCreatedEventHandler>(
[this](ICoreWebView2* sender,
ICoreWebView2FrameCreatedEventArgs* args) -> HRESULT
{
wil::com_ptr<ICoreWebView2Frame> webviewFrame;
CHECK_FAILURE(args->get_Frame(&webviewFrame));
auto frame10 = webviewFrame
.try_query<ICoreWebView2ExperimentalFrame10>();
if (!frame10)
{
return S_OK;
}
CHECK_FAILURE(frame10->add_LaunchingExternalUriScheme(
Callback<
ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler>(
[this](
ICoreWebView2Frame* frameSender,
ICoreWebView2LaunchingExternalUriSchemeEventArgs2*
args) -> HRESULT
{
// Avoid reentrancy by scheduling the dialog
// 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<ICoreWebView2Deferral> deferral;
CHECK_FAILURE(args->GetDeferral(&deferral));
wil::com_ptr<ICoreWebView2Frame> 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 attempting to launch an external URI scheme for ";
message += uri.get();
message += L".\n\nAllow this action?";
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#
private WebView2 m_webview;
void RegisterFrameLaunchingExternalUriSchemeHandler()
{
m_webview.CoreWebView2.FrameCreated += (sender, frameCreatedArgs) =>
{
frameCreatedArgs.Frame.LaunchingExternalUriScheme +=
async (frameSender, args) =>
{
// Because asynchronous work is awaited below, set `Handled`
// 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 " +
$"attempting to launch an external URI scheme for " +
$"{args.Uri}.\n\nAllow this action?";
MessageBoxResult selection = MessageBox.Show(
message,
"Launching External URI Scheme",
MessageBoxButton.YesNo);
args.Cancel = selection != MessageBoxResult.Yes;
}
};
};
}
API Details
Win32 (C++)
interface ICoreWebView2ExperimentalFrame10;
interface ICoreWebView2ExperimentalFrameLaunchingExternalUriSchemeEventHandler;
interface ICoreWebView2LaunchingExternalUriSchemeEventArgs2;
/// Extends the `ICoreWebView2Frame` interface to expose the
/// `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
/// 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 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 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 by
/// `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 {
/// Invoked when the corresponding event is raised.
HRESULT Invoke(
[in] ICoreWebView2Frame* sender,
[in] ICoreWebView2LaunchingExternalUriSchemeEventArgs2* args);
};
/// 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`.
///
/// 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.
[propput] HRESULT Handled([in] BOOL value);
};.Net/C#
namespace Microsoft.Web.WebView2.Core
{
runtimeclass CoreWebView2LaunchingExternalUriSchemeEventArgs
{
// ...
[interface_name(
"Microsoft.Web.WebView2.Core.ICoreWebView2LaunchingExternalUriSchemeEventArgs2")]
{
[doc_string(
"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, this "
"property must be set to TRUE synchronously before "
"taking the deferral.")]
Boolean Handled { get; set; };
}
}
runtimeclass CoreWebView2Frame
{
// ...
[interface_name(
"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.")]
event Windows.Foundation.TypedEventHandler<
CoreWebView2Frame,
CoreWebView2LaunchingExternalUriSchemeEventArgs>
LaunchingExternalUriScheme;
}
}
}There was a problem hiding this comment.
Thanks — applied the rewrite. Adopted the structure and wording, kept the reference links and the Appendix.
Add
FrameLaunchingExternalUriScheme.md— proposes raising theLaunchingExternalUriSchemeevent onCoreWebView2Frame(in addition to the existing event onCoreWebView2) so host apps can attribute external-URI launches to a specific iframe via the event sender. Also proposes aHandledproperty onCoreWebView2LaunchingExternalUriSchemeEventArgsso per-iframe handlers can suppress the webview-level handler.Same shape as
CoreWebView2Frame.PermissionRequested+CoreWebView2PermissionRequestedEventArgs2.Handled.