Skip to content

Commit 63dbaa5

Browse files
authored
Merge pull request #826 from MicrosoftEdge/api-browserprocessexited-pid
Add browser PID to BrowserProcessExitedEventArgs
2 parents 9551fc0 + 08a319b commit 63dbaa5

1 file changed

Lines changed: 124 additions & 63 deletions

File tree

specs/BrowserProcessExited.md

Lines changed: 124 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ additional work on the host app, so we are proposing the `BrowserProcessExited`
55
event. The `ProcessFailed` event already lets app developers handle unexpected
66
browser process exits for a WebView; this new API lets you listen to both
77
expected and unexpected termination of the collection of processes associated to
8-
a `CoreWebView2Environment` so you can, e.g., cleanup the user data folder when
8+
a `CoreWebView2Environment` so you can, for example, cleanup the user data folder when
99
it's no longer in use. In this document we describe the new API. We'd appreciate
1010
your feedback.
1111

@@ -19,7 +19,7 @@ process is active, and moving to a new WebView2 Runtime version after a
1919
`NewBrowserVersionAvailable` event.
2020

2121
This event is raised for both expected and unexpected termination of the
22-
collection of processes, after all resources, including the user data folder,
22+
collection of processes, after all resources, including the user data folder,
2323
used by the WebView2 Runtime have been released. The
2424
`CoreWebView2BrowserProcessExitedEventArgs` lets app developers get
2525
the `BrowserProcessExitKind` so they can decide how to handle different exit
@@ -32,14 +32,17 @@ different scenarios. It is up to the app to coordinate the handlers so they do
3232
not try to perform reliability recovery while also trying to move to a new
3333
WebView2 Runtime version or remove the user data folder.
3434

35-
Multiple app processes can share a browser process by creating their
36-
`CoreWebView2Environment` with the same user data folder. When the entire
37-
collection of WebView2Runtime processes sharing the browser process exit, all
38-
associated `CoreWebview2Environment` objects receive the `BrowserProcessExited`
35+
Multiple app processes can share a browser process by creating their webviews
36+
from a `CoreWebView2Environment` with the same user data folder. When the entire
37+
collection of WebView2Runtime processes for the browser process exit, all
38+
associated `CoreWebView2Environment` objects receive the `BrowserProcessExited`
3939
event. Multiple processes sharing the same browser process need to coordinate
40-
their use of the shared user data folder to avoid race conditions. For example,
41-
one process should not clear the user data folder at the same time that another
42-
process recovers from a crash by recreating its WebView controls.
40+
their use of the shared user data folder to avoid race conditions and
41+
unnecessary waits. For example, one process should not clear the user data
42+
folder at the same time that another process recovers from a crash by recreating
43+
its WebView controls; one process should not block waiting for the event if
44+
other app processes are using the same browser process (the browser process will
45+
not exit until those other processes have closed their webviews too).
4346

4447

4548
# Examples
@@ -53,6 +56,24 @@ class AppWindow {
5356

5457
wil::com_ptr<ICoreWebView2Controller> m_controller;
5558
EventRegistrationToken m_browserExitedEventToken = {};
59+
UINT32 m_newestBrowserPid = 0;
60+
}
61+
62+
HRESULT AppWindow::OnCreateCoreWebView2ControllerCompleted(HRESULT result, ICoreWebView2Controller* controller)
63+
{
64+
if (result == S_OK)
65+
{
66+
// ...
67+
68+
// Save PID of the browser process serving last WebView created from our
69+
// CoreWebView2Environment. We know the controller was created with
70+
// S_OK, and it hasn't been closed (we haven't called Close and no
71+
// ProcessFailed event could have been raised yet) so the PID is
72+
// available.
73+
CHECK_FAILURE(m_webView->get_BrowserProcessId(&m_newestBrowserPid));
74+
75+
// ...
76+
}
5677
}
5778

5879
void AppWindow::CloseWebView(/* ... */) {
@@ -66,25 +87,49 @@ void AppWindow::CloseWebView(/* ... */) {
6687
ICoreWebView2Environment* sender,
6788
ICoreWebView2BrowserProcessExitedEventArgs* args) {
6889
COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND kind;
90+
UINT32 pid;
6991
CHECK_FAILURE(args->get_BrowserProcessExitKind(&kind));
70-
71-
// Watch for graceful browser process exit. Let ProcessFailed event
72-
// handler take care of failed browser process termination.
73-
if (kind == COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND_NORMAL)
92+
CHECK_FAILURE(args->get_BrowserProcessId(&pid));
93+
94+
// If a new WebView is created from this CoreWebView2Environment after
95+
// the browser has exited but before our handler gets to run, a new
96+
// browser process will be created and lock the user data folder
97+
// again. Do not attempt to cleanup the user data folder in these
98+
// cases. If this happens, the PID of the new browser process will be
99+
// different to the PID of the older process, we check against the
100+
// PID of the browser process to which our last CoreWebView2 attached.
101+
if (pid == m_newestBrowserPid)
102+
{
103+
// Watch for graceful browser process exit. Let ProcessFailed event
104+
// handler take care of failed browser process termination.
105+
if (kind == COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND_NORMAL)
106+
{
107+
CHECK_FAILURE(
108+
m_webViewEnvironment->remove_BrowserProcessExited(m_browserExitedEventToken));
109+
// Release the environment only after the handler is invoked.
110+
// Otherwise, there will be no environment to raise the event when
111+
// the collection of WebView2 Runtime processes exit.
112+
m_webViewEnvironment = nullptr;
113+
CleanupUserDataFolder();
114+
}
115+
}
116+
else
74117
{
75-
CHECK_FAILURE(
76-
m_webViewEnvironment->remove_BrowserProcessExited(m_browserExitedEventToken));
77-
// Release the environment only after the handler is invoked.
78-
// Otherwise, there will be no environment to raise the event when
79-
// the collection of WebView2 Runtime processes exit.
80-
m_webViewEnvironment = nullptr;
81-
CleanupUserDataFolder();
118+
// The exiting process is not the last in use. Do not attempt cleanup
119+
// as we might still have a webview open over the user data folder.
120+
// Do not block from event handler.
121+
RunAsync([this]() {
122+
MessageBox(
123+
m_mainWindow,
124+
L"A new browser process prevented cleanup of the user data folder.",
125+
L"Cleanup User Data Folder", MB_OK);
126+
});
82127
}
83128

84129
return S_OK;
85130
}).Get(),
86131
&m_browserExitedEventToken));
87-
132+
88133
// ...
89134

90135
// Close the WebView from its controller.
@@ -117,7 +162,7 @@ void AppWindow::CloseWebView(/* ... */) {
117162
<!-- ... -->
118163
119164
<DockPanel DockPanel.Dock="Top">
120-
165+
121166
<!-- ... -->
122167
123168
<TextBox x:Name="url" Text="{Binding ElementName=webView,Path=Source,Mode=OneWay}">
@@ -227,12 +272,13 @@ void ReinitializeWebView()
227272

228273
# Remarks
229274
Note this is an event from `CoreWebView2Environment`, not `CoreWebView2`. The
230-
difference between this `BrowserProcessExited` event and the `CoreWebView2`'s
231-
`ProcessFailed` event is that `BrowserProcessExited` is raised for any (expected
232-
and unexpected) **browser process** (along its associated processes) exits,
233-
while `ProcessFailed` is raised only for **unexpected** browser process exits,
234-
or for **render process** exits/unresponsiveness. To learn more about the
235-
WebView2 Process Model, go to [Process model](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model).
275+
difference between `BrowserProcessExited` and `CoreWebView2`'s `ProcessFailed`
276+
is that `BrowserProcessExited` is raised for any **browser process** exit
277+
(expected or unexpected, after all associated processes have exited too), while
278+
`ProcessFailed` is raised for **unexpected** process exits of any kind (browser,
279+
render, GPU, and all other types), or for main frame **render process**
280+
unresponsiveness. To learn more about the WebView2 Process Model, go to
281+
[Process model](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model).
236282

237283
In the case the browser process crashes, both `BrowserProcessExited` and
238284
`ProcessFailed` events are raised, but the order is not guaranteed.
@@ -268,31 +314,36 @@ interface ICoreWebView2Environment3 : ICoreWebView2Environment2
268314

269315
/// Add an event handler for the `BrowserProcessExited` event.
270316
/// The `BrowserProcessExited` event is raised when the collection of WebView2
271-
/// Runtime processes associated to this environment terminate due to a
272-
/// browser process error or normal shutdown (e.g., when all associated
273-
/// WebViews are closed), after all resources (including the user data folder)
274-
/// have been released.
317+
/// Runtime processes for the browser process of this environment terminate
318+
/// due to browser process failure or normal shutdown (for example, when all
319+
/// associated WebViews are closed), after all resources have been released
320+
/// (including the user data folder). To learn about what these processes are,
321+
/// go to [Process model](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model).
275322
///
276323
/// A handler added with this method is called until removed with
277324
/// `remove_BrowserProcessExited`, even if a new browser process is bound to
278325
/// this environment after earlier `BrowserProcessExited` events are raised.
279326
///
280-
/// Multiple app processes can share a browser process by creating their
281-
/// `ICoreWebView2Environment` with the same user data folder. When the entire
282-
/// collection of WebView2Runtime processes sharing the browser process exit, all
283-
/// associated `ICoreWebview2Environment` objects receive the `BrowserProcessExited`
327+
/// Multiple app processes can share a browser process by creating their webviews
328+
/// from a `ICoreWebView2Environment` with the same user data folder. When the entire
329+
/// collection of WebView2Runtime processes for the browser process exit, all
330+
/// associated `ICoreWebView2Environment` objects receive the `BrowserProcessExited`
284331
/// event. Multiple processes sharing the same browser process need to coordinate
285-
/// their use of the shared user data folder to avoid race conditions. For example,
286-
/// one process should not clear the user data folder at the same time that another
287-
/// process recovers from a crash by recreating its WebView controls.
332+
/// their use of the shared user data folder to avoid race conditions and
333+
/// unnecessary waits. For example, one process should not clear the user data
334+
/// folder at the same time that another process recovers from a crash by recreating
335+
/// its WebView controls; one process should not block waiting for the event if
336+
/// other app processes are using the same browser process (the browser process will
337+
/// not exit until those other processes have closed their webviews too).
288338
///
289-
/// Note this is an event from the `ICoreWebView2Environment3` interface, not the
290-
/// `ICoreWebView2`. The difference between this `BrowserProcessExited` event and
291-
/// the `ICoreWebView2`'s `ProcessFailed` event is that `BrowserProcessExited` is
292-
/// raised for any (expected and unexpected) **browser process** (along its
293-
/// associated processes) exits, while `ProcessFailed` is raised only for
294-
/// **unexpected** browser process exits, or for **render process**
295-
/// exits/unresponsiveness. To learn more about the WebView2 Process Model, go to
339+
/// Note this is an event from the `ICoreWebView2Environment3` interface, not
340+
/// the `ICoreWebView2` one. The difference between `BrowserProcessExited` and
341+
/// `ICoreWebView2`'s `ProcessFailed` is that `BrowserProcessExited` is
342+
/// raised for any **browser process** exit (expected or unexpected, after all
343+
/// associated processes have exited too), while `ProcessFailed` is raised for
344+
/// **unexpected** process exits of any kind (browser, render, GPU, and all
345+
/// other types), or for main frame **render process** unresponsiveness. To
346+
/// learn more about the WebView2 Process Model, go to
296347
/// [Process model](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model).
297348
///
298349
/// In the case the browser process crashes, both `BrowserProcessExited` and
@@ -324,6 +375,8 @@ interface ICoreWebView2BrowserProcessExitedEventArgs : IUnknown
324375
/// The kind of browser process exit that has occurred.
325376
[propget] HRESULT BrowserProcessExitKind(
326377
[out, retval] COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND* browserProcessExitKind);
378+
/// The process ID of the browser process that has exited.
379+
[propget] HRESULT BrowserProcessId([out, retval] UINT32* value);
327380
}
328381
```
329382

@@ -351,26 +404,31 @@ namespace Microsoft.Web.WebView2.Core
351404
// ...
352405
353406
/// `BrowserProcessExited` is raised when the collection of WebView2
354-
/// Runtime processes associated to this `CoreWebView2Environment` terminate due to a
355-
/// browser process error or normal shutdown (e.g., when all associated
356-
/// WebViews are closed), after all resources (including the user data folder)
357-
/// have been released.
407+
/// Runtime processes for the browser process of this `CoreWebView2Environment`
408+
/// terminate due to browser process failure or normal shutdown (for example,
409+
/// when all associated WebViews are closed), after all resources have been
410+
/// released (including the user data folder). To learn about what these processes
411+
/// are, go to [Process model](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model).
358412
///
359-
/// Multiple app processes can share a browser process by creating their
360-
/// `CoreWebView2Environment` with the same user data folder. When the entire
361-
/// collection of WebView2Runtime processes sharing the browser process exit, all
362-
/// associated `CoreWebview2Environment` objects receive the `BrowserProcessExited`
413+
/// Multiple app processes can share a browser process by creating their webviews
414+
/// from a `CoreWebView2Environment` with the same user data folder. When the entire
415+
/// collection of WebView2Runtime processes for the browser process exit, all
416+
/// associated `CoreWebView2Environment` objects receive the `BrowserProcessExited`
363417
/// event. Multiple processes sharing the same browser process need to coordinate
364-
/// their use of the shared user data folder to avoid race conditions. For example,
365-
/// one process should not clear the user data folder at the same time that another
366-
/// process recovers from a crash by recreating its WebView controls.
418+
/// their use of the shared user data folder to avoid race conditions and
419+
/// unnecessary waits. For example, one process should not clear the user data
420+
/// folder at the same time that another process recovers from a crash by recreating
421+
/// its WebView controls; one process should not block waiting for the event if
422+
/// other app processes are using the same browser process (the browser process will
423+
/// not exit until those other processes have closed their webviews too).
367424
///
368425
/// Note this is an event from `CoreWebView2Environment`, not `CoreWebView2`. The
369-
/// difference between this `BrowserProcessExited` event and the `CoreWebView2`'s
370-
/// `ProcessFailed` event is that `BrowserProcessExited` is raised for any (expected
371-
/// and unexpected) **browser process** (along its associated processes) exits, while
372-
/// `ProcessFailed` is raised only for **unexpected** browser process exits, or for
373-
/// **render process** exits/unresponsiveness. To learn more about the WebView2
426+
/// difference between `BrowserProcessExited` and `CoreWebView2`'s
427+
/// `ProcessFailed` is that `BrowserProcessExited` is raised for any **browser process** exit
428+
/// (expected or unexpected, after all associated processes have exited too), while
429+
/// `ProcessFailed` is raised for **unexpected** process exits of any kind (browser,
430+
/// render, GPU, and all other types), or for main frame **render process**
431+
/// unresponsiveness. To learn more about the WebView2
374432
/// Process Model, go to [Process model](https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/process-model).
375433
///
376434
/// In the case the browser process crashes, both `BrowserProcessExited` and
@@ -387,6 +445,9 @@ namespace Microsoft.Web.WebView2.Core
387445
{
388446
/// The kind of browser process exit that has occurred.
389447
CoreWebView2BrowserProcessExitKind BrowserProcessExitKind { get; };
448+
449+
/// The process ID of the browser process that has exited.
450+
UInt32 BrowserProcessId { get; };
390451
}
391452
}
392453
```
@@ -398,7 +459,7 @@ handler for `BrowserProcessExited` will only be interested in the next single
398459
time the browser process exits (even if there could be more browser processes
399460
being created and exiting throughout the lifetime of a
400461
`CoreWebView2Environment`). For this reason, we also consider making this event
401-
an async method instead (e.g., `RegisterWaitForBrowserProcessExit`).
462+
an async method instead (for example, `RegisterWaitForBrowserProcessExit`).
402463

403464
While there would be no operation started on calling the async method, a handler
404465
would be a added to be run (only) the next time the browser process associated

0 commit comments

Comments
 (0)