|
| 1 | +# Background |
| 2 | +We have heard asks for a WebView2 API to easily track the WebView2 Runtime's |
| 3 | +browser process exit. Manually waiting for the process to exit requires |
| 4 | +additional work on the host app, so we are proposing the `BrowserProcessExited` |
| 5 | +event. The `ProcessFailed` event already lets app developers handle unexpected |
| 6 | +browser process exits for a WebView, this new API lets you listen to both |
| 7 | +expected and unexpected termination of the processes associated to an |
| 8 | +environment from the `ICoreWebView2Environment3` interface so you can, e.g., |
| 9 | +cleanup the user data folder when it's no longer in use. In this document we |
| 10 | +describe the new API. We'd appreciate your feedback. |
| 11 | + |
| 12 | + |
| 13 | +# Description |
| 14 | +The `BrowserProcessExited` event allows developers to subscribe event handlers |
| 15 | +to be run when the WebView2 Runtime processes (including the browser process) |
| 16 | +associated to a `CoreWebView2Environment` terminate. Key scenarios are cleanup |
| 17 | +of the user data folder used by the WebView2 Runtime, which is locked while the |
| 18 | +runtime's browser process is active, and moving to a new WebView2 Runtime |
| 19 | +version after a `NewBrowserVersionAvailable` event. |
| 20 | + |
| 21 | +This event is raised for both expected and unexpected browser process |
| 22 | +termination, after all resources, including the user data folder, used by the |
| 23 | +browser process (and related processes) have been released. The |
| 24 | +`ICoreWebView2BrowserProcessExitedEventArgs` interface lets app developers get |
| 25 | +the `BrowserProcessExitKind` so they can decide how to handle different exit |
| 26 | +kinds or bypass handling if an event handler for the `CoreWebView2`s |
| 27 | +`ProcessFailed` event (for `CoreWebView2ProcessFailedKind.BrowserProcessFailed`) |
| 28 | +is already registered. In case of a browser process crash, both |
| 29 | +`BrowserProcessExited` and `ProcessFailed` events are raised, but the order is |
| 30 | +not guaranteed. |
| 31 | + |
| 32 | +All `CoreWebView2Environment` objects across different app processes that use |
| 33 | +the same browser process receive this event when the browser process (and |
| 34 | +associated processes) exits. If the browser process (and therefore the user data |
| 35 | +folder) in use by the app process (through the `CoreWebView2Environment` options |
| 36 | +used) is shared with other processes, these processes need to coordinate to |
| 37 | +handle the potential race condition on the use of the resources. E.g., if one |
| 38 | +app process tries to clear the user data folder, while other tries to recreate |
| 39 | +its WebViews on crash. |
| 40 | + |
| 41 | + |
| 42 | +# Examples |
| 43 | +The following code snippets demonstrate how the `BrowserProcessExited` event can |
| 44 | +be used: |
| 45 | + |
| 46 | +## Win32 C++ |
| 47 | +```cpp |
| 48 | +// Before closing the WebView, register a handler with code to run once the |
| 49 | +// browser process is terminated. |
| 50 | +EventRegistrationToken browserExitedEventToken; |
| 51 | + |
| 52 | +CHECK_FAILURE(m_webViewEnvironment->add_BrowserProcessExited( |
| 53 | + Callback<ICoreWebView2BrowserProcessExitedEventHandler>( |
| 54 | + [browserExitedEventToken, this]( |
| 55 | + ICoreWebView2Environment* sender, |
| 56 | + ICoreWebView2BrowserProcessExitedEventArgs* args) { |
| 57 | + COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND kind; |
| 58 | + CHECK_FAILURE(args->get_BrowserProcessExitKind(&kind)); |
| 59 | + |
| 60 | + // Watch for graceful browser process exit. Let ProcessFailed event |
| 61 | + // handler take care of failed browser process termination. |
| 62 | + if (kind == COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND_NORMAL) |
| 63 | + { |
| 64 | + CHECK_FAILURE( |
| 65 | + m_webViewEnvironment->remove_BrowserProcessExited(browserExitedEventToken)); |
| 66 | + // Release the environment only after the handler is invoked. |
| 67 | + // Otherwise, there will be no environment to raise the event when |
| 68 | + // the process exits. |
| 69 | + m_webViewEnvironment = nullptr; |
| 70 | + CleanupUserDataFolder(); |
| 71 | + } |
| 72 | + |
| 73 | + return S_OK; |
| 74 | + }).Get(), |
| 75 | + &browserExitedEventToken)); |
| 76 | +``` |
| 77 | +
|
| 78 | +## .NET C# |
| 79 | +```c# |
| 80 | +// URI or other state to save/restore when the WebView is recreated. |
| 81 | +private Uri _uriToRestore; |
| 82 | +
|
| 83 | +async void RegisterForNewVersion() |
| 84 | +{ |
| 85 | + // We need to make sure the CoreWebView2 property is not null, so we can get |
| 86 | + // the environment from it. Alternatively, if the WebView was created from |
| 87 | + // an environment provided to the control, we can use that environment |
| 88 | + // object directly. |
| 89 | + await webView.EnsureCoreWebView2Async(); |
| 90 | + _coreWebView2Environment = webView.CoreWebView2.Environment; |
| 91 | + _coreWebView2Environment.NewBrowserVersionAvailable += Environment_NewBrowserVersionAvailable; |
| 92 | +} |
| 93 | +
|
| 94 | +// A new version of the WebView2 Runtime is available, our handler gets called. |
| 95 | +// We close our WebView and set a handler to reinitialize it once the browser |
| 96 | +// process is gone, so we get the new version of the WebView2 Runtime. |
| 97 | +void Environment_NewBrowserVersionAvailable(object sender, object e) |
| 98 | +{ |
| 99 | + StringBuilder messageBuilder = new StringBuilder(256); |
| 100 | + messageBuilder.Append("We detected there is a new version of the WebView2 Runtime installed. "); |
| 101 | + messageBuilder.Append("Do you want to switch to it now? This will re-create the WebView."); |
| 102 | + var selection = MessageBox.Show(this, messageBuilder.ToString(), "New WebView2 Runtime detected", MessageBoxButton.YesNo); |
| 103 | + if (selection == MessageBoxResult.Yes) |
| 104 | + { |
| 105 | + // Save URI or other state you want to restore when the WebView is recreated. |
| 106 | + _uriToRestore = webView.Source; |
| 107 | + _coreWebView2Environment.BrowserProcessExited += Environment_BrowserProcessExited; |
| 108 | + // We dispose of the control so the internal WebView objects are released |
| 109 | + // and the associated browser process exits. If there are any other WebViews |
| 110 | + // from the same environment configuration, they need to be closed too. |
| 111 | + webView.Dispose(); |
| 112 | + webView = null; |
| 113 | + } |
| 114 | +} |
| 115 | +
|
| 116 | +void Environment_BrowserProcessExited(object sender, CoreWebView2BrowserProcessExitedEventArgs e) |
| 117 | +{ |
| 118 | + ((CoreWebView2Environment)sender).BrowserProcessExited -= Environment_BrowserProcessExited; |
| 119 | + ReinitializeWebView(); |
| 120 | +} |
| 121 | +
|
| 122 | +void ReinitializeWebView() |
| 123 | +{ |
| 124 | + webView = new WebView2(); |
| 125 | +
|
| 126 | + // Restore URI and other WebView state/setup. |
| 127 | + webView.CreationProperties = (CoreWebView2CreationProperties)this.FindResource("EvergreenWebView2CreationProperties"); |
| 128 | + webView.NavigationStarting += WebView_NavigationStarting; |
| 129 | + webView.NavigationCompleted += WebView_NavigationCompleted; |
| 130 | +
|
| 131 | + Binding urlBinding = new Binding() |
| 132 | + { |
| 133 | + Source = webView, |
| 134 | + Path = new PropertyPath("Source"), |
| 135 | + Mode = BindingMode.OneWay |
| 136 | + }; |
| 137 | + url.SetBinding(TextBox.TextProperty, urlBinding); |
| 138 | +
|
| 139 | + MyWindow.MyDockPanel.Children.Add(webView); |
| 140 | + webView.Source = (_uriToRestore != null) ? _uriToRestore : new Uri("https://www.bing.com"); |
| 141 | + RegisterForNewVersion(); |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | + |
| 146 | +# Remarks |
| 147 | +Note this is an event from the `ICoreWebView2Environment3` interface, not the |
| 148 | +`ICoreWebView2`. The difference between this `BrowserProcessExited` event and |
| 149 | +the `CoreWebView2`'s `ProcessFailed` event is that `BrowserProcessExited` is |
| 150 | +raised for any (expected and unexpected) **browser process** (along its |
| 151 | +associated processes) exits, while `ProcessFailed` is raised only for |
| 152 | +**unexpected** browser process exits, or for **render process** |
| 153 | +exits/unresponsiveness. To learn more about the WebView2 Process Model, go to |
| 154 | +[Process model](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model). |
| 155 | + |
| 156 | +In the case the browser process crashes, both `BrowserProcessExited` and |
| 157 | +`ProcessFailed` events are raised, but the order is not guaranteed. |
| 158 | + |
| 159 | + |
| 160 | +# API Notes |
| 161 | +See [API Details](#api-details) section below for API reference. |
| 162 | + |
| 163 | + |
| 164 | +# API Details |
| 165 | + |
| 166 | +## COM |
| 167 | +```cpp |
| 168 | +library WebView2 |
| 169 | +{ |
| 170 | +// ... |
| 171 | + |
| 172 | +/// Specifies the browser process exit type used in the |
| 173 | +/// `ICoreWebView2BrowserProcessExitedEventArgs` interface. |
| 174 | +typedef enum COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND { |
| 175 | + /// Indicates that the browser process ended normally. |
| 176 | + COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND_NORMAL, |
| 177 | + |
| 178 | + /// Indicates that the browser process ended unexpectedly. |
| 179 | + /// A `ProcessFailed` event will also be sent to listening WebViews from the |
| 180 | + /// `ICoreWebView2Environment` associated to the failed process. |
| 181 | + COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND_FAILED |
| 182 | +} COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND; |
| 183 | + |
| 184 | +interface ICoreWebView2Environment3 : ICoreWebView2Environment2 |
| 185 | +{ |
| 186 | + // ... |
| 187 | + |
| 188 | + /// Add an event handler for the `BrowserProcessExited` event. |
| 189 | + /// The `BrowserProcessExited` event is raised when the browser process of the |
| 190 | + /// WebView2 Runtime associated to this environment terminates due to an error |
| 191 | + /// or normal shutdown (e.g., when all its WebViews are closed), after all |
| 192 | + /// resources (including the user data folder) used by the browser process |
| 193 | + /// (and related processes) have been released. |
| 194 | + /// |
| 195 | + /// A handler added with this method is called until removed with |
| 196 | + /// `remove_BrowserProcessExited`, even if a new browser process is bound to |
| 197 | + /// this environment after earlier `BrowserProcessExited` events are raised. |
| 198 | + /// |
| 199 | + /// All `CoreWebView2Environment` objects across different app processes that use |
| 200 | + /// the same browser process receive this event when the browser process (and |
| 201 | + /// associated processes) exits. If the browser process (and therefore the user data |
| 202 | + /// folder) in use by the app process (through the `CoreWebView2Environment` options |
| 203 | + /// used) is shared with other processes, these processes need to coordinate to |
| 204 | + /// handle the potential race condition on the use of the resources. E.g., if one |
| 205 | + /// app process tries to clear the user data folder, while other tries to recreate |
| 206 | + /// its WebViews on crash. |
| 207 | + /// |
| 208 | + /// Note this is an event from the `ICoreWebView2Environment3` interface, not the |
| 209 | + /// `ICoreWebView2`. The difference between this `BrowserProcessExited` event and |
| 210 | + /// the `CoreWebView2`'s `ProcessFailed` event is that `BrowserProcessExited` is |
| 211 | + /// raised for any (expected and unexpected) **browser process** (along its |
| 212 | + /// associated processes) exits, while `ProcessFailed` is raised only for |
| 213 | + /// **unexpected** browser process exits, or for **render process** |
| 214 | + /// exits/unresponsiveness. To learn more about the WebView2 Process Model, go to |
| 215 | + /// [Process model](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model). |
| 216 | + /// |
| 217 | + /// In the case the browser process crashes, both `BrowserProcessExited` and |
| 218 | + /// `ProcessFailed` events are raised, but the order is not guaranteed. |
| 219 | + HRESULT add_BrowserProcessExited( |
| 220 | + [in] ICoreWebView2BrowserProcessExitedEventHandler* eventHandler, |
| 221 | + [out] EventRegistrationToken* token); |
| 222 | + |
| 223 | + /// Remove an event handler previously added with `add_BrowserProcessExited`. |
| 224 | + HRESULT remove_BrowserProcessExited([in] EventRegistrationToken token); |
| 225 | +} |
| 226 | + |
| 227 | +/// Receives `BrowserProcessExited` events. |
| 228 | +interface ICoreWebView2BrowserProcessExitedEventHandler : IUnknown |
| 229 | +{ |
| 230 | + /// Provides the event args for the corresponding event. |
| 231 | + HRESULT Invoke( |
| 232 | + [in] ICoreWebView2Environment* sender, |
| 233 | + [in] ICoreWebView2BrowserProcessExitedEventArgs* args); |
| 234 | +} |
| 235 | + |
| 236 | +/// Event args for the `BrowserProcessExited` event. |
| 237 | +interface ICoreWebView2BrowserProcessExitedEventArgs : IUnknown |
| 238 | +{ |
| 239 | + /// The kind of browser process exit that has occurred. |
| 240 | + [propget] HRESULT BrowserProcessExitKind( |
| 241 | + [out, retval] COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND* browserProcessExitKind); |
| 242 | +} |
| 243 | +``` |
| 244 | + |
| 245 | +## .NET and WinRT |
| 246 | +```c# |
| 247 | +namespace Microsoft.Web.WebView2.Core |
| 248 | +{ |
| 249 | + // ... |
| 250 | +
|
| 251 | + /// Specifies the browser process exit kind used in |
| 252 | + /// `CoreWebView2BrowserProcessExitedEventArgs`. |
| 253 | + enum CoreWebView2BrowserProcessExitKind |
| 254 | + { |
| 255 | + /// Indicates that the browser process ended normally. |
| 256 | + Normal, |
| 257 | + /// Indicates that the browser process ended unexpectedly. |
| 258 | + /// A `CoreWebView2.ProcessFailed` event will also be raised to |
| 259 | + /// listening WebViews from the `CoreWebView2Environment` associated to |
| 260 | + /// the failed process. |
| 261 | + Failed |
| 262 | + }; |
| 263 | + |
| 264 | + runtimeclass CoreWebView2Environment |
| 265 | + { |
| 266 | + // ... |
| 267 | +
|
| 268 | + /// `BrowserProcessExited` is raised when the browser process of the |
| 269 | + /// WebView2 Runtime associated to this `CoreWebView2Environment` |
| 270 | + /// terminates due to an error or normal shutdown (e.g., when all its |
| 271 | + /// WebViews are closed), after all resources (including the user data |
| 272 | + /// folder) used by the browser process (and related processes) have |
| 273 | + /// been released. |
| 274 | + /// |
| 275 | + /// All `CoreWebView2Environment` objects across different app processes that use |
| 276 | + /// the same browser process receive this event when the browser process (and |
| 277 | + /// associated processes) exits. If the browser process (and therefore the user data |
| 278 | + /// folder) in use by the app process (through the `CoreWebView2Environment` options |
| 279 | + /// used) is shared with other processes, these processes need to coordinate to |
| 280 | + /// handle the potential race condition on the use of the resources. E.g., if one |
| 281 | + /// app process tries to clear the user data folder, while other tries to recreate |
| 282 | + /// its WebViews on crash. |
| 283 | + /// |
| 284 | + /// Note this is an event from the `ICoreWebView2Environment3` interface, not the |
| 285 | + /// `ICoreWebView2`. The difference between this `BrowserProcessExited` event and |
| 286 | + /// the `CoreWebView2`'s `ProcessFailed` event is that `BrowserProcessExited` is |
| 287 | + /// raised for any (expected and unexpected) **browser process** (along its |
| 288 | + /// associated processes) exits, while `ProcessFailed` is raised only for |
| 289 | + /// **unexpected** browser process exits, or for **render process** |
| 290 | + /// exits/unresponsiveness. To learn more about the WebView2 Process Model, go to |
| 291 | + /// [Process model](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model). |
| 292 | + /// |
| 293 | + /// In the case the browser process crashes, both `BrowserProcessExited` and |
| 294 | + /// `ProcessFailed` events are raised, but the order is not guaranteed. |
| 295 | + event Windows.Foundation.TypedEventHandler<CoreWebView2Environment, CoreWebView2BrowserProcessExitedEventArgs> BrowserProcessExited; |
| 296 | + } |
| 297 | + |
| 298 | + /// Event args for the `CoreWebView2Environment.BrowserProcessExited` event. |
| 299 | + runtimeclass CoreWebView2BrowserProcessExitedEventArgs |
| 300 | + { |
| 301 | + /// The kind of browser process exit that has occurred. |
| 302 | + CoreWebView2BrowserProcessExitKind BrowserProcessExitKind { get; }; |
| 303 | + } |
| 304 | +} |
| 305 | +``` |
| 306 | + |
| 307 | +# Appendix |
| 308 | +We expect that for the two scenarios this API is designed for, namely cleanup of |
| 309 | +the user data folder and upgrading the WebView2 Runtime, an app adding a |
| 310 | +handler for `BrowserProcessExited` will only be interested in the next single |
| 311 | +time the browser process exits (even if there could be more browser processes |
| 312 | +being created and exiting throughout the lifetime of a |
| 313 | +`CoreWebView2Environment`). For this reason, we also consider making this event |
| 314 | +an async method instead (e.g., `RegisterWaitForBrowserProcessExit`). |
| 315 | + |
| 316 | +While there would be no operation started on calling the async method, a handler |
| 317 | +would be a added to be run (only) the next time the browser process associated |
| 318 | +to the `CoreWebView2Environment` exits, which in turn would make API usage |
| 319 | +easier for the two expected scenarios. |
| 320 | + |
| 321 | +Alternatively, this could be kept an event and the registered handlers be |
| 322 | +automatically removed the next time the event is raised. |
0 commit comments