Skip to content

Commit

Permalink
feat: webview2 composition support
Browse files Browse the repository at this point in the history
  • Loading branch information
yydcnjjw committed Sep 7, 2024
1 parent 9ad972e commit 1c21975
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 99 deletions.
12 changes: 10 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,7 @@ pub(crate) struct PlatformSpecificWebViewAttributes {
theme: Option<Theme>,
use_https: bool,
scroll_bar_style: ScrollBarStyle,
composition: bool,
}

#[cfg(windows)]
Expand All @@ -1102,6 +1103,7 @@ impl Default for PlatformSpecificWebViewAttributes {
theme: None,
use_https: false, // To match macOS & Linux behavior in the context of mixed content.
scroll_bar_style: ScrollBarStyle::default(),
composition: false,
}
}
}
Expand Down Expand Up @@ -1153,6 +1155,7 @@ pub trait WebViewBuilderExtWindows {
/// Requires WebView2 Runtime version 125.0.2535.41 or higher, does nothing on older versions,
/// see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/?tabs=dotnetcsharp#10253541
fn with_scroll_bar_style(self, style: ScrollBarStyle) -> Self;
fn with_composition(self, enabled: bool) -> Self;
}

#[cfg(windows)]
Expand Down Expand Up @@ -1181,6 +1184,11 @@ impl WebViewBuilderExtWindows for WebViewBuilder<'_> {
self.platform_specific.scroll_bar_style = style;
self
}

fn with_composition(mut self, enabled: bool) -> Self {
self.platform_specific.composition = enabled;
self
}
}

#[cfg(target_os = "android")]
Expand All @@ -1190,7 +1198,7 @@ pub(crate) struct PlatformSpecificWebViewAttributes {
Option<Box<dyn Fn(prelude::Context) -> std::result::Result<(), jni::errors::Error> + Send>>,
with_asset_loader: bool,
asset_loader_domain: Option<String>,
https_scheme: bool,
use_https: bool,
}

#[cfg(target_os = "android")]
Expand Down Expand Up @@ -1249,7 +1257,7 @@ impl WebViewBuilderExtAndroid for WebViewBuilder<'_> {
}

fn with_https_scheme(mut self, enabled: bool) -> Self {
self.platform_specific.https_scheme = enabled;
self.platform_specific.use_https = enabled;
self
}
}
Expand Down
185 changes: 88 additions & 97 deletions src/webview2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ impl From<windows::core::Error> for Error {
}

pub(crate) struct InnerWebView {
parent: RefCell<HWND>,
parent: Option<RefCell<HWND>>,
hwnd: HWND,
is_child: bool,
pub controller: ICoreWebView2Controller,
webview: ICoreWebView2,
env: ICoreWebView2Environment,
Expand All @@ -65,10 +64,10 @@ pub(crate) struct InnerWebView {
impl Drop for InnerWebView {
fn drop(&mut self) {
let _ = unsafe { self.controller.Close() };
if self.is_child {
if let Some(parent) = &self.parent {
let _ = unsafe { DestroyWindow(self.hwnd) };
unsafe { Self::dettach_parent_subclass(*parent.borrow()) }
}
unsafe { Self::dettach_parent_subclass(*self.parent.borrow()) }
}
}

Expand All @@ -84,7 +83,18 @@ impl InnerWebView {
RawWindowHandle::Win32(window) => HWND(window.hwnd.get() as _),
_ => return Err(Error::UnsupportedWindowHandle),
};
Self::new_in_hwnd(window, attributes, pl_attrs, web_context, false)

let this = Self::new_in_hwnd(window, attributes, pl_attrs, web_context)?;

let mut rect = RECT::default();
unsafe { GetClientRect(this.hwnd, &mut rect)? };
let width = rect.right - rect.left;
let height = rect.bottom - rect.top;

this.set_bounds_inner((width, height).into(), (0, 0).into())?;

unsafe { Self::attach_parent_subclass(this.hwnd, &this.controller) };
Ok(this)
}

#[inline]
Expand All @@ -99,63 +109,49 @@ impl InnerWebView {
_ => return Err(Error::UnsupportedWindowHandle),
};

Self::new_in_hwnd(parent, attributes, pl_attrs, web_context, true)
let hwnd = Self::create_child_container_hwnd(parent, &attributes)?;

let bounds = attributes.bounds.clone();

let mut this = Self::new_in_hwnd(hwnd, attributes, pl_attrs, web_context)?;
this.parent = Some(RefCell::new(parent));

this.set_bounds(bounds.unwrap_or_default())?;
Ok(this)
}

#[inline]
fn new_in_hwnd(
parent: HWND,
hwnd: HWND,
mut attributes: WebViewAttributes,
pl_attrs: super::PlatformSpecificWebViewAttributes,
web_context: Option<&mut WebContext>,
is_child: bool,
) -> Result<Self> {
let _ = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) };

let hwnd = Self::create_container_hwnd(parent, &attributes, is_child)?;

let drop_handler = attributes.drag_drop_handler.take();
let bounds = attributes.bounds;

let env = Self::create_environment(&web_context, pl_attrs.clone(), &attributes)?;
let controller = Self::create_controller(hwnd, &env, attributes.incognito)?;
let webview = Self::init_webview(
parent,
hwnd,
attributes,
&env,
&controller,
pl_attrs,
is_child,
)?;
let controller =
Self::create_controller(hwnd, &env, attributes.incognito, pl_attrs.composition)?;
let webview = Self::init_webview(hwnd, attributes, &env, &controller, pl_attrs)?;

let drag_drop_controller = drop_handler.map(|handler| DragDropController::new(hwnd, handler));

let w = Self {
parent: RefCell::new(parent),
parent: None,
hwnd,
controller,
is_child,
webview,
env,
drag_drop_controller,
};

if is_child {
w.set_bounds(bounds.unwrap_or_default())?;
} else {
w.resize_to_parent()?;
}

Ok(w)
}

#[inline]
fn create_container_hwnd(
parent: HWND,
attributes: &WebViewAttributes,
is_child: bool,
) -> Result<HWND> {
fn create_child_container_hwnd(parent: HWND, attributes: &WebViewAttributes) -> Result<HWND> {
unsafe extern "system" fn default_window_proc(
hwnd: HWND,
msg: u32,
Expand Down Expand Up @@ -192,7 +188,7 @@ impl InnerWebView {
let dpi = unsafe { util::hwnd_dpi(parent) };
let scale_factor = util::dpi_to_scale_factor(dpi);

let (x, y, width, height) = if is_child {
let (x, y, width, height) = {
let (x, y) = attributes
.bounds
.map(|b| b.position.to_physical::<f64>(scale_factor))
Expand All @@ -205,12 +201,6 @@ impl InnerWebView {
.unwrap_or((CW_USEDEFAULT, CW_USEDEFAULT));

(x, y, width, height)
} else {
let mut rect = RECT::default();
unsafe { GetClientRect(parent, &mut rect)? };
let width = rect.right - rect.left;
let height = rect.bottom - rect.top;
(0, 0, width, height)
};

let hwnd = unsafe {
Expand Down Expand Up @@ -330,50 +320,80 @@ impl InnerWebView {
hwnd: HWND,
env: &ICoreWebView2Environment,
incognito: bool,
composition: bool,
) -> Result<ICoreWebView2Controller> {
let (tx, rx) = mpsc::channel();
let env = env.clone();
let env10 = env.cast::<ICoreWebView2Environment10>();

CreateCoreWebView2ControllerCompletedHandler::wait_for_async_operation(
if let Ok(env10) = env10 {
let controller_opts = unsafe { env10.CreateCoreWebView2ControllerOptions()? };
unsafe { controller_opts.SetIsInPrivateModeEnabled(incognito)? }
Box::new(
move |handler: ICoreWebView2CreateCoreWebView2ControllerCompletedHandler| unsafe {
if composition {
CreateCoreWebView2CompositionControllerCompletedHandler::wait_for_async_operation(
if let Ok(env10) = env10 {
let controller_opts = unsafe { env10.CreateCoreWebView2ControllerOptions()? };
unsafe { controller_opts.SetIsInPrivateModeEnabled(incognito)? }
Box::new(move |handler| unsafe {
env10
.CreateCoreWebView2ControllerWithOptions(hwnd, &controller_opts, &handler)
.CreateCoreWebView2CompositionControllerWithOptions(hwnd, &controller_opts, &handler)
.map_err(Into::into)
},
)
} else {
Box::new(
move |handler: ICoreWebView2CreateCoreWebView2ControllerCompletedHandler| unsafe {
env
.CreateCoreWebView2Controller(hwnd, &handler)
})
} else if let Ok(env3) = env.cast::<ICoreWebView2Environment3>() {
Box::new(move |handler| unsafe {
env3
.CreateCoreWebView2CompositionController(hwnd, &handler)
.map_err(Into::into)
},
)
},
Box::new(move |error_code, controller| {
error_code?;
tx.send(controller.ok_or_else(|| windows::core::Error::from(E_POINTER)))
})
} else {
return Err(windows::core::Error::from(E_UNEXPECTED).into());
},
Box::new(move |error_code, controller| {
error_code?;
tx.send(
controller
.ok_or_else(|| windows::core::Error::from(E_POINTER))
.and_then(|c| c.cast::<ICoreWebView2Controller>()),
)
.map_err(|_| windows::core::Error::from(E_UNEXPECTED))
}),
)?;
}),
)?;
} else {
CreateCoreWebView2ControllerCompletedHandler::wait_for_async_operation(
if let Ok(env10) = env10 {
let controller_opts = unsafe { env10.CreateCoreWebView2ControllerOptions()? };
unsafe { controller_opts.SetIsInPrivateModeEnabled(incognito)? }
Box::new(
move |handler: ICoreWebView2CreateCoreWebView2ControllerCompletedHandler| unsafe {
env10
.CreateCoreWebView2ControllerWithOptions(hwnd, &controller_opts, &handler)
.map_err(Into::into)
},
)
} else {
Box::new(
move |handler: ICoreWebView2CreateCoreWebView2ControllerCompletedHandler| unsafe {
env
.CreateCoreWebView2Controller(hwnd, &handler)
.map_err(Into::into)
},
)
},
Box::new(move |error_code, controller| {
error_code?;
tx.send(controller.ok_or_else(|| windows::core::Error::from(E_POINTER)))
.map_err(|_| windows::core::Error::from(E_UNEXPECTED))
}),
)?;
}

rx.recv()?.map_err(Into::into)
}

#[inline]
fn init_webview(
parent: HWND,
hwnd: HWND,
mut attributes: WebViewAttributes,
env: &ICoreWebView2Environment,
controller: &ICoreWebView2Controller,
pl_attrs: super::PlatformSpecificWebViewAttributes,
is_child: bool,
) -> Result<ICoreWebView2> {
let webview = unsafe { controller.CoreWebView2()? };

Expand Down Expand Up @@ -484,11 +504,6 @@ impl InnerWebView {
unsafe { webview.NavigateToString(&html)? };
}

// Subclass parent for resizing and focus
if !is_child {
unsafe { Self::attach_parent_subclass(parent, controller) };
}

unsafe {
controller.SetIsVisible(attributes.visible)?;

Expand Down Expand Up @@ -1056,7 +1071,7 @@ impl InnerWebView {
});

let mut hwnd = HWND::default();
if (*controller).ParentWindow(&mut hwnd).is_ok() {
if (*controller).ParentWindow(&mut hwnd).is_err() {
let _ = SetWindowPos(
hwnd,
HWND::default(),
Expand Down Expand Up @@ -1207,14 +1222,14 @@ impl InnerWebView {
pub fn bounds(&self) -> Result<Rect> {
let mut bounds = Rect::default();
let mut rect = RECT::default();
if self.is_child {
if let Some(parent) = &self.parent {
unsafe { GetClientRect(self.hwnd, &mut rect)? };

let position_point = &mut [POINT {
x: rect.left,
y: rect.top,
}];
unsafe { MapWindowPoints(self.hwnd, *self.parent.borrow(), position_point) };
unsafe { MapWindowPoints(self.hwnd, *parent.borrow(), position_point) };

bounds.position = PhysicalPosition::new(position_point[0].x, position_point[0].y).into();
} else {
Expand Down Expand Up @@ -1262,15 +1277,6 @@ impl InnerWebView {
Ok(())
}

fn resize_to_parent(&self) -> crate::Result<()> {
let mut rect = RECT::default();
unsafe { GetClientRect(*self.parent.borrow(), &mut rect)? };
let width = rect.right - rect.left;
let height = rect.bottom - rect.top;

self.set_bounds_inner((width, height).into(), (0, 0).into())
}

pub fn set_visible(&self, visible: bool) -> Result<()> {
unsafe {
let _ = ShowWindow(
Expand Down Expand Up @@ -1301,21 +1307,6 @@ impl InnerWebView {

unsafe {
SetParent(self.hwnd, parent)?;

if !self.is_child {
Self::dettach_parent_subclass(*self.parent.borrow());
Self::attach_parent_subclass(parent, &self.controller);

*self.parent.borrow_mut() = parent;

let mut rect = RECT::default();
GetClientRect(parent, &mut rect)?;

let width = rect.right - rect.left;
let height = rect.bottom - rect.top;

self.set_bounds_inner((width, height).into(), (0, 0).into())?;
}
}

Ok(())
Expand Down

0 comments on commit 1c21975

Please sign in to comment.