Skip to content

Commit 14f2b56

Browse files
Merge pull request #3144 from MicrosoftEdge/api-launchingregisteredprotocols
API spec CoreWebView2.LaunchingExternalUriScheme
2 parents eb127dc + 5d60c08 commit 14f2b56

File tree

1 file changed

+339
-0
lines changed

1 file changed

+339
-0
lines changed

specs/LaunchingExternalUriScheme.md

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
# Background
2+
3+
We are exposing an event that will be raised when an attempt to launch a URI scheme that is registered with the OS (external URI scheme) is made.
4+
When navigating to a URI, the URI scheme determines how to handle the URI.
5+
Some schemes like http, and https, are resolved by the WebView2 and the navigation is handled in the WebView2.
6+
Other URI schemes may be registered externally to the WebView2 with the OS by other applications.
7+
Such schemes are handled by launching the external URI scheme.
8+
9+
The host will be given the option to cancel the external URI scheme launch with the `LaunchingExternalUriScheme` event.
10+
Cancelling the launch gives the host the opportunity to hide the default dialog, display a custom dialog, and then launch the external URI scheme themselves.
11+
12+
# Description
13+
14+
This event will be raised before the external URI scheme launch occurs.
15+
When an attempt to launch an external URI scheme is made and the host does not cancel the event, the default dialog is displayed in which the user can select `Open` or `Cancel`.
16+
The `NavigationStarting` event will be raised before the `LaunchingExternalUriScheme` event, followed by the `NavigationCompleted` event. The `NavigationCompleted` event will be raised with the `IsSuccess` property set to `FALSE` and the `WebErrorStatus` property set to `ConnectionAborted` regardless of whether the host sets the `Cancel` property on the `ICoreWebView2LaunchingExternalUriSchemeEventArgs`. The `SourceChanged`, `ContentLoading`, and `HistoryChanged` events will not be raised when a request is made to launch an external URI scheme and the `WebView2.Source` property remains unchanged regardless of the `ICoreWebView2LaunchingExternalUriSchemeEventArgs`.
17+
18+
The `LaunchingExternalUriScheme` event will be raised on the `CoreWebView2` interface.
19+
20+
# Examples
21+
22+
## Win32 C++
23+
24+
```cpp
25+
26+
AppWindow* m_appWindow;
27+
wil::com_ptr<ICoreWebView2> m_webView;
28+
EventRegistrationToken m_LaunchingExternalUriSchemeToken = {};
29+
30+
void RegisterLaunchingExternalUriSchemeHandler()
31+
{
32+
auto webView16 = m_webView.try_query<ICoreWebView2_16>();
33+
if (webView16)
34+
{
35+
CHECK_FAILURE(webView16->add_LaunchingExternalUriScheme(
36+
Callback<ICoreWebView2LaunchingExternalUriSchemeEventHandler>(
37+
[this](
38+
ICoreWebView2* sender,
39+
ICoreWebView2LaunchingExternalUriSchemeEventArgs* args)
40+
{
41+
auto showDialog = [this, args]
42+
{
43+
wil::unique_cotaskmem_string uri;
44+
CHECK_FAILURE(args->get_Uri(&uri));
45+
if (wcsicmp(uri.get(), L"calculator://") == 0)
46+
{
47+
// Set the event args to cancel the event and launch the
48+
// calculator app. This will always allow the external URI scheme launch.
49+
args->put_Cancel(true);
50+
std::wstring schemeUrl = L"calculator://";
51+
SHELLEXECUTEINFO info = {sizeof(info)};
52+
info.fMask = SEE_MASK_NOASYNC;
53+
info.lpVerb = L"open";
54+
info.lpFile = schemeUrl.c_str();
55+
info.nShow = SW_SHOWNORMAL;
56+
::ShellExecuteEx(&info);
57+
}
58+
else if (wcsicmp(uri.get(), L"malicious://") == 0)
59+
{
60+
// Always block the request in this case by cancelling the event.
61+
args->put_Cancel(true);
62+
}
63+
else if (wcsicmp(uri.get(), L"contoso://") == 0)
64+
{
65+
// To display a custom dialog we cancel the launch, display
66+
// a custom dialog, and then manually launch the external URI scheme
67+
// depending on the user's selection.
68+
args->put_Cancel(true);
69+
wil::unique_cotaskmem_string initiatingOrigin;
70+
CHECK_FAILURE(args->get_InitiatingOrigin(&initiatingOrigin));
71+
std::wstring message = L"Launching External URI Scheme request";
72+
std::wstring initiatingOriginString =
73+
initiatingOrigin.get();
74+
if (initiatingOriginString.empty())
75+
{
76+
message += L" from ";
77+
message += initiatingOriginString;
78+
}
79+
message += L" to ";
80+
message += uri.get();
81+
message += L"?\n\n";
82+
message += L"Do you want to grant permission?\n";
83+
int response = MessageBox(
84+
nullptr, message.c_str(), L"Launching External URI Scheme",
85+
MB_YESNO | MB_ICONWARNING);
86+
if (response == IDYES)
87+
{
88+
std::wstring schemeUrl = uri.get();
89+
SHELLEXECUTEINFO info = {sizeof(info)};
90+
info.fMask = SEE_MASK_NOASYNC;
91+
info.lpVerb = L"open";
92+
info.lpFile = schemeUrl.c_str();
93+
info.nShow = SW_SHOWNORMAL;
94+
::ShellExecuteEx(&info);
95+
}
96+
}
97+
else
98+
{
99+
// Do not cancel the event, allowing the request to use the default dialog.
100+
}
101+
return S_OK;
102+
};
103+
showDialog();
104+
return S_OK;
105+
// A deferral may be taken for the event so that the CoreWebView2
106+
// doesn't examine the properties we set on the event args until
107+
// after we call the Complete method asynchronously later.
108+
// This will give the user more time to decide whether to launch
109+
// the external URI scheme or not.
110+
// wil::com_ptr<ICoreWebView2Deferral> deferral;
111+
// CHECK_FAILURE(args->GetDeferral(&deferral));
112+
113+
// m_appWindow->RunAsync(
114+
// [deferral, showDialog]()
115+
// {
116+
// showDialog();
117+
// CHECK_FAILURE(deferral->Complete());
118+
// });
119+
// return S_OK;
120+
})
121+
.Get(),
122+
&m_LaunchingExternalUriSchemeToken));
123+
}
124+
}
125+
126+
```
127+
128+
## .NET and WinRT
129+
130+
```c#
131+
private WebView2 webView;
132+
void RegisterLaunchingExternalUriSchemeHandler()
133+
{
134+
webView.CoreWebView2.LaunchingExternalUriScheme += (sender, args)
135+
{
136+
// A deferral may be taken for the event so that the CoreWebView2
137+
// doesn't examine the properties we set on the event args until
138+
// after we call the Complete method asynchronously later.
139+
// This will give the user more time to decide whether to launch
140+
// the external URI scheme or not.
141+
// CoreWebView2Deferral deferral = args.GetDeferral();
142+
// System.Threading.SynchronizationContext.Current.Post((_) =>
143+
// {
144+
// using (deferral)
145+
// {
146+
if (String.Equals(args.Uri, "calculator:///", StringComparison.OrdinalIgnoreCase))
147+
{
148+
// Set the event args to cancel the event and launch the
149+
// calculator app. This will always allow the external URI scheme launch.
150+
args.Cancel = true;
151+
ProcessStartInfo info = new ProcessStartInfo
152+
{
153+
FileName = args.Uri,
154+
UseShellExecute = true
155+
};
156+
Process.Start(info);
157+
}
158+
else if (String.Equals(args.Uri, "malicious:///", StringComparison.OrdinalIgnoreCase)) {
159+
// Always block the request in this case by cancelling the event.
160+
args.Cancel = true;
161+
}
162+
else if (String.Equals(args.Uri, "contoso:///", StringComparison.OrdinalIgnoreCase))
163+
{
164+
// To display a custom dialog we cancel the launch, display
165+
// a custom dialog, and then manually launch the external URI scheme
166+
// depending on the user's selection.
167+
args.Cancel = true;
168+
string text = "Launching External URI Scheme";
169+
if (args.InitiatingOrigin != "")
170+
{
171+
text += "from ";
172+
text += args.InitiatingOrigin;
173+
}
174+
text += " to ";
175+
text += args.Uri;
176+
text += "\n";
177+
text += "Do you want to grant permission?";
178+
string caption = "Launching External URI Scheme request";
179+
MessageBoxButton btnMessageBox = MessageBoxButton.YesNo;
180+
MessageBoxImage icnMessageBox = MessageBoxImage.None;
181+
MessageBoxResult resultbox = MessageBox.Show(text, caption, btnMessageBox, icnMessageBox);
182+
switch (resultbox)
183+
{
184+
case MessageBoxResult.Yes:
185+
ProcessStartInfo info = new ProcessStartInfo
186+
{
187+
FileName = args.Uri,
188+
UseShellExecute = true
189+
};
190+
Process.Start(info);
191+
break;
192+
193+
case MessageBoxResult.No:
194+
break;
195+
}
196+
197+
}
198+
else
199+
{
200+
// Do not cancel the event, allowing the request to use the default dialog.
201+
}
202+
// }
203+
// }, null);
204+
};
205+
}
206+
207+
```
208+
209+
# API Details
210+
211+
## Win32 C++
212+
213+
```IDL
214+
// This is the ICoreWebView2_16 interface.
215+
[uuid(cc39bea3-d6d8-471b-919f-da253e2fbf03), object, pointer_default(unique)]
216+
interface ICoreWebView2_16 : ICoreWebView2_15 {
217+
/// Add an event handler for the `LaunchingExternalUriScheme` event.
218+
/// The `LaunchingExternalUriScheme` event is raised when a navigation request is made to
219+
/// a URI scheme that is registered with the OS.
220+
/// The `LaunchingExternalUriScheme` event handler may suppress the default dialog
221+
/// or replace the default dialog with a custom dialog.
222+
///
223+
/// If a deferral is not taken on the event args, the external URI scheme launch is
224+
/// blocked until the event handler returns. If a deferral is taken, the
225+
/// external URI scheme launch is blocked until the deferral is completed.
226+
/// The host also has the option to cancel the URI scheme launch.
227+
///
228+
/// The `NavigationStarting` and `NavigationCompleted` events will be raised,
229+
/// regardless of whether the `Cancel` property is set to `TRUE` or
230+
/// `FALSE`. The `NavigationCompleted` event will be raised with the `IsSuccess` property
231+
/// set to `FALSE` and the `WebErrorStatus` property set to `ConnectionAborted` regardless of
232+
/// whether the host sets the `Cancel` property on the
233+
/// `ICoreWebView2LaunchingExternalUriSchemeEventArgs`. The `SourceChanged`, `ContentLoading`,
234+
/// and `HistoryChanged` events will not be raised for this navigation to the external URI
235+
/// scheme regardless of the `Cancel` property.
236+
/// The `LaunchingExternalUriScheme` event will be raised after the
237+
/// `NavigationStarting` event and before the `NavigationCompleted` event.
238+
/// The default `CoreWebView2Settings` will also be updated upon navigation to an external
239+
/// URI scheme. If a setting on the `CoreWebView2Settings` interface has been changed,
240+
/// navigating to an external URI scheme will trigger the `CoreWebView2Settings` to update.
241+
///
242+
/// The WebView2 may not display the default dialog based on user settings, browser settings,
243+
/// and whether the origin is determined as a
244+
/// [trustworthy origin](https://w3c.github.io/webappsec-secure-contexts#
245+
/// potentially-trustworthy-origin); however, the event will still be raised.
246+
///
247+
/// If the request is initiated by a cross-origin frame without a user gesture,
248+
/// the request will be blocked and the `LaunchingExternalUriScheme` event will not
249+
/// be raised.
250+
/// \snippet SettingsComponent.cpp LaunchingExternalUriScheme
251+
HRESULT add_LaunchingExternalUriScheme(
252+
[in] ICoreWebView2LaunchingExternalUriSchemeEventHandler* eventHandler,
253+
[out] EventRegistrationToken* token);
254+
255+
/// Remove an event handler previously added with
256+
/// `add_LaunchingExternalUriScheme`.
257+
HRESULT remove_LaunchingExternalUriScheme(
258+
[in] EventRegistrationToken token);
259+
}
260+
261+
/// Event handler for the `LaunchingExternalUriScheme` event.
262+
[uuid(e5fea648-79c9-47aa-8314-f471fe627649), object, pointer_default(unique)]
263+
interface ICoreWebView2LaunchingExternalUriSchemeEventHandler: IUnknown {
264+
/// Receives the event args for the corresponding event.
265+
HRESULT Invoke(
266+
[in] ICoreWebView2* sender,
267+
[in] ICoreWebView2LaunchingExternalUriSchemeEventArgs* args);
268+
}
269+
270+
/// Event args for `LaunchingExternalUriScheme` event.
271+
[uuid(fc43b557-9713-4a67-af8d-a76ef3a206e8), object, pointer_default(unique)]
272+
interface ICoreWebView2LaunchingExternalUriSchemeEventArgs: IUnknown {
273+
/// The URI with the external URI scheme to be launched.
274+
275+
[propget] HRESULT Uri([out, retval] LPWSTR* value);
276+
277+
/// The origin initiating the external URI scheme launch.
278+
/// The origin will be an empty string if the request is initiated by calling
279+
/// `CoreWebView2.Navigate` on the external URI scheme. If a script initiates
280+
/// the navigation, the `InitiatingOrigin` will be the top-level document's
281+
/// `Source`, i.e. if `window.location` is set to `"calculator://", the `InitiatingOrigin`
282+
/// will be set to `calculator://`. If the request is initiated from a child frame, the
283+
/// `InitiatingOrigin` will be the source of that child frame.
284+
285+
[propget] HRESULT InitiatingOrigin([out, retval] LPWSTR* value);
286+
287+
/// `TRUE` when the external URI scheme request was initiated through a user gesture.
288+
///
289+
/// \> [!NOTE]\n\> Being initiated through a user gesture does not mean that user intended
290+
/// to access the associated resource.
291+
292+
[propget] HRESULT IsUserInitiated([out, retval] BOOL* value);
293+
294+
/// The event handler may set this property to `TRUE` to cancel the external URI scheme
295+
/// launch. If set to `TRUE`, the external URI scheme will not be launched, and the default
296+
/// dialog is not displayed. This property can be used to replace the normal
297+
/// handling of launching an external URI scheme.
298+
/// The initial value of the `Cancel` property is `FALSE`.
299+
300+
[propget] HRESULT Cancel([out, retval] BOOL* value);
301+
302+
/// Sets the `Cancel` property.
303+
304+
[propput] HRESULT Cancel([in] BOOL value);
305+
306+
/// Returns an `ICoreWebView2Deferral` object. Use this operation to
307+
/// complete the event at a later time.
308+
309+
HRESULT GetDeferral([out, retval] ICoreWebView2Deferral** value);
310+
}
311+
312+
```
313+
## .NET and WinRT
314+
315+
```c#
316+
namespace Microsoft.Web.WebView2.Core
317+
{
318+
runtimeclass CoreWebView2LaunchingExternalUriSchemeEventArgs;
319+
320+
runtimeclass CoreWebView2LaunchingExternalUriSchemeEventArgs
321+
{
322+
// CoreWebView2LaunchingExternalUriSchemeEventArgs members
323+
String Uri { get; };
324+
String InitiatingOrigin { get; };
325+
Boolean IsUserInitiated { get; };
326+
Boolean Cancel { get; set; };
327+
Windows.Foundation.Deferral GetDeferral();
328+
}
329+
330+
runtimeclass CoreWebView2
331+
{
332+
// CoreWebView2
333+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_16")]
334+
{
335+
event Windows.Foundation.TypedEventHandler<CoreWebView2, CoreWebView2LaunchingExternalUriSchemeEventArgs> LaunchingExternalUriScheme;
336+
}
337+
}
338+
}
339+
```

0 commit comments

Comments
 (0)