Skip to content

Commit e5d7eca

Browse files
Merge pull request #2029 from MicrosoftEdge/api-launchingregisteredprotocols-draft
Api launchingregisteredprotocols draft
2 parents a56f59d + 86c05a5 commit e5d7eca

1 file changed

Lines changed: 318 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)