|
| 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