use std::{
io,
sync::mpsc::{self, Receiver},
time::Duration,
};
use webview2_com::{
BrowserProcessExitedEventHandler,
Microsoft::Web::WebView2::Win32::{
COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND_NORMAL, ICoreWebView2,
ICoreWebView2BrowserProcessExitedEventHandler, ICoreWebView2Environment,
ICoreWebView2Environment5,
},
};
use windows::core::Interface;
use super::super::super::pump::wait_for;
use super::super::error::{callback_context, callback_error, windows_error};
pub(super) struct BrowserExit {
environment: ICoreWebView2Environment5,
receiver: Receiver<windows::core::Result<ExitObservation>>,
token: i64,
_handler: ICoreWebView2BrowserProcessExitedEventHandler,
}
enum ExitObservation {
Exited(u32),
Failed(windows::core::Error),
}
impl BrowserExit {
pub(super) fn register(
webview: &ICoreWebView2,
environment: &ICoreWebView2Environment,
) -> io::Result<Self> {
let mut expected = 0;
unsafe { webview.BrowserProcessId(&mut expected) }.map_err(windows_error)?;
if expected == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"WebView2 owned browser has an invalid process ID",
));
}
let environment = environment
.cast::<ICoreWebView2Environment5>()
.map_err(|error| {
io::Error::other(format!("WebView2 owned-profile exit interface: {error}"))
})?;
let (sender, receiver) = mpsc::sync_channel(1);
let handler = BrowserProcessExitedEventHandler::create(Box::new(move |_sender, args| {
let result = (|| {
let args =
args.ok_or_else(|| callback_error("WebView2 browser exit omitted arguments"))?;
let mut actual = 0;
let mut kind = COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND_NORMAL;
unsafe { args.BrowserProcessId(&mut actual) }
.map_err(|error| callback_context("WebView2 exited browser PID", error))?;
unsafe { args.BrowserProcessExitKind(&mut kind) }
.map_err(|error| callback_context("WebView2 browser exit kind", error))?;
if actual != expected {
return Err(callback_error(
"WebView2 owned profile reported a different browser PID",
));
}
if kind != COREWEBVIEW2_BROWSER_PROCESS_EXIT_KIND_NORMAL {
return Err(callback_error("WebView2 owned browser exited abnormally"));
}
tracing::info!(
browser_process_id = actual,
"WebView2 owned profile browser resources released"
);
Ok(actual)
})();
let observation = match result {
Ok(pid) => ExitObservation::Exited(pid),
Err(error) => ExitObservation::Failed(error),
};
sender.try_send(Ok(observation)).map_err(|error| {
windows::core::Error::new(
windows::Win32::Foundation::E_ABORT,
format!("WebView2 browser exit observation channel: {error}"),
)
})
}));
let mut token = 0;
unsafe { environment.add_BrowserProcessExited(&handler, &mut token) }
.map_err(windows_error)?;
Ok(Self {
environment,
receiver,
token,
_handler: handler,
})
}
pub(super) fn wait(self, timeout: Duration) -> io::Result<()> {
let result = wait_for(self.receiver, timeout)
.and_then(|observation| match observation {
ExitObservation::Exited(pid) => {
tracing::info!(
browser_process_id = pid,
"WebView2 owned browser shutdown observed"
);
Ok(())
}
ExitObservation::Failed(error) => Err(io::Error::other(format!(
"WebView2 owned browser exit observation: {error}"
))),
})
.map_err(|error| {
io::Error::new(
error.kind(),
format!("WebView2 owned-profile browser shutdown: {error}"),
)
});
let removal =
unsafe { self.environment.remove_BrowserProcessExited(self.token) }.map_err(|error| {
io::Error::other(format!("WebView2 remove browser exit observer: {error}"))
});
match (result, removal) {
(Ok(()), Ok(())) => Ok(()),
(Err(error), Ok(())) | (Ok(()), Err(error)) => Err(error),
(Err(wait), Err(removal)) => {
Err(io::Error::new(wait.kind(), format!("{wait}; {removal}")))
}
}
}
}