use super::*;
mod dcomp;
mod dragdrop;
mod pointer;
mod surface_window;
use dcomp::DcompTree;
pub(crate) enum HostingMode {
Windowed,
Composition(Box<CompositionSurface>),
}
pub(crate) struct CompositionSurface {
pub(crate) hwnd: HWND,
env3: ICoreWebView2Environment3,
controller: ICoreWebView2CompositionController,
dcomp: DcompTree,
input_tokens: surface_window::InputSubscriptions,
parent: HWND,
radii: [i32; 4],
corner_color: u32,
bounds: RECT,
visible: bool,
}
static COMPOSITION_HOSTING: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(true);
pub fn set_webview_composition_hosting(enabled: bool) {
COMPOSITION_HOSTING.store(enabled, std::sync::atomic::Ordering::Relaxed);
}
pub fn webview_composition_hosting_enabled() -> bool {
composition_hosting_enabled()
}
fn composition_hosting_enabled() -> bool {
let configured = || COMPOSITION_HOSTING.load(std::sync::atomic::Ordering::Relaxed);
match std::env::var("LINGXIA_WEBVIEW_COMPOSITION") {
Ok(value) => match value.trim().to_ascii_lowercase().as_str() {
"0" | "false" | "off" => false,
"1" | "true" | "on" => true,
_ => configured(),
},
Err(_) => configured(),
}
}
pub(crate) fn create_hosting_controller(
env: &ICoreWebView2Environment,
parent: HWND,
) -> StdResult<(ICoreWebView2Controller, HostingMode)> {
if composition_hosting_enabled() {
match create_composition_surface(env, parent) {
Ok((controller, surface)) => {
return Ok((controller, HostingMode::Composition(surface)));
}
Err(err) => {
log::warn!("composition hosting unavailable; using windowed WebView2: {err}");
}
}
}
Ok((create_controller(env, parent)?, HostingMode::Windowed))
}
fn create_composition_surface(
env: &ICoreWebView2Environment,
parent: HWND,
) -> StdResult<(ICoreWebView2Controller, Box<CompositionSurface>)> {
let env3: ICoreWebView2Environment3 = env.cast().map_err(|err| {
WebViewError::WebView(format!("WebView2 runtime lacks composition hosting: {err}"))
})?;
let mut bounds = RECT::default();
unsafe {
WindowsAndMessaging::GetClientRect(parent, &mut bounds)
.map_err(|err| WebViewError::WebView(format!("GetClientRect failed: {err}")))?;
}
let hwnd = surface_window::create_surface_window(parent, bounds)?;
let assembled = (|| {
let dcomp = DcompTree::new(hwnd)?;
let controller = create_composition_controller(&env3, hwnd)?;
let input_tokens = attach_surface(hwnd, &dcomp, &env3, &controller)?;
let base: ICoreWebView2Controller = controller.cast().map_err(|err| {
WebViewError::WebView(format!("composition controller cast failed: {err}"))
})?;
Ok((
base,
Box::new(CompositionSurface {
hwnd,
env3,
controller,
dcomp,
input_tokens,
parent,
radii: [0; 4],
corner_color: 0,
bounds,
visible: false,
}),
))
})();
if assembled.is_err() {
unsafe {
let _ = WindowsAndMessaging::DestroyWindow(hwnd);
}
}
assembled
}
fn attach_surface(
hwnd: HWND,
dcomp: &DcompTree,
env3: &ICoreWebView2Environment3,
controller: &ICoreWebView2CompositionController,
) -> StdResult<surface_window::InputSubscriptions> {
unsafe {
controller
.SetRootVisualTarget(dcomp.webview_visual())
.map_err(|err| WebViewError::WebView(format!("SetRootVisualTarget failed: {err}")))?;
}
let base: ICoreWebView2Controller = controller.cast().map_err(|err| {
WebViewError::WebView(format!("composition controller cast failed: {err}"))
})?;
let tokens = surface_window::attach_input(hwnd, env3, controller, &base);
dragdrop::register_drop_target(hwnd, controller);
Ok(tokens)
}
fn create_composition_controller(
env3: &ICoreWebView2Environment3,
hwnd: HWND,
) -> StdResult<ICoreWebView2CompositionController> {
let env3 = env3.clone();
let (tx, rx) = mpsc::channel();
CreateCoreWebView2CompositionControllerCompletedHandler::wait_for_async_operation(
Box::new(move |handler| unsafe {
env3.CreateCoreWebView2CompositionController(hwnd, &handler)
.map_err(webview2_com::Error::WindowsError)
}),
Box::new(move |result, controller| {
result?;
tx.send(controller.ok_or_else(|| windows::core::Error::from(E_POINTER)))
.map_err(|_| windows::core::Error::from(E_POINTER))?;
Ok(())
}),
)
.map_err(map_webview2_error)?;
rx.recv()
.map_err(|_| {
WebViewError::WebView("Composition controller callback channel failed".to_string())
})?
.map_err(|err| {
WebViewError::WebView(format!("Composition controller creation failed: {err}"))
})
}
impl CompositionSurface {
fn ensure_alive(&mut self, parent: HWND, base: &ICoreWebView2Controller) -> StdResult<bool> {
if unsafe { WindowsAndMessaging::IsWindow(Some(self.hwnd)).as_bool() } {
return Ok(false);
}
log::info!("composition surface window died with its former host; recreating");
surface_window::detach_input(&self.controller, base, self.input_tokens);
self.input_tokens = surface_window::InputSubscriptions::default();
let hwnd = surface_window::create_surface_window(parent, self.bounds)?;
let rebuilt = (|| {
let dcomp = DcompTree::new(hwnd)?;
let tokens = attach_surface(hwnd, &dcomp, &self.env3, &self.controller)?;
Ok((dcomp, tokens))
})();
let (dcomp, tokens) = match rebuilt {
Ok(parts) => parts,
Err(err) => {
unsafe {
let _ = WindowsAndMessaging::DestroyWindow(hwnd);
}
return Err(err);
}
};
self.hwnd = hwnd;
self.dcomp = dcomp;
self.input_tokens = tokens;
self.parent = parent;
let bounds = self.bounds;
self.set_geometry(base, bounds, None)?;
if self.visible {
self.set_visible(base, true)?;
}
Ok(true)
}
pub(crate) fn set_geometry(
&mut self,
base: &ICoreWebView2Controller,
bounds: RECT,
corners: Option<([i32; 4], u32)>,
) -> StdResult<()> {
if !unsafe { WindowsAndMessaging::IsWindow(Some(self.hwnd)).as_bool() } {
self.bounds = bounds;
if let Some((radii, corner_color)) = corners {
self.radii = radii;
self.corner_color = corner_color;
}
let parent = self.parent;
return self.ensure_alive(parent, base).map(|_| ());
}
let (radii, corner_color) = corners.unwrap_or((self.radii, self.corner_color));
let width = (bounds.right - bounds.left).max(0);
let height = (bounds.bottom - bounds.top).max(0);
unsafe {
WindowsAndMessaging::SetWindowPos(
self.hwnd,
None,
bounds.left,
bounds.top,
width,
height,
WindowsAndMessaging::SWP_NOZORDER | WindowsAndMessaging::SWP_NOACTIVATE,
)
.map_err(|err| WebViewError::WebView(format!("SetWindowPos failed: {err}")))?;
base.SetBounds(RECT {
left: 0,
top: 0,
right: width,
bottom: height,
})
.map_err(|err| WebViewError::WebView(format!("SetBounds failed: {err}")))?;
}
self.bounds = bounds;
self.radii = radii;
self.corner_color = corner_color;
self.dcomp
.apply_geometry(width, height, radii, corner_color)
}
pub(crate) fn set_visible(
&mut self,
base: &ICoreWebView2Controller,
visible: bool,
) -> StdResult<()> {
self.visible = visible;
unsafe {
if visible {
surface_window::cancel_hide_suspend(self.hwnd);
let result = base
.SetIsVisible(true)
.map_err(|err| WebViewError::WebView(format!("SetIsVisible failed: {err}")));
let _ = WindowsAndMessaging::ShowWindow(self.hwnd, WindowsAndMessaging::SW_SHOWNA);
result
} else {
let _ = WindowsAndMessaging::ShowWindow(self.hwnd, WindowsAndMessaging::SW_HIDE);
surface_window::schedule_hide_suspend(self.hwnd);
Ok(())
}
}
}
pub(crate) fn set_parent(
&mut self,
base: &ICoreWebView2Controller,
parent: HWND,
) -> StdResult<()> {
unsafe {
let rebuilt = self.ensure_alive(parent, base)?;
if !rebuilt && self.parent != parent {
WindowsAndMessaging::SetParent(self.hwnd, Some(parent))
.map_err(|err| WebViewError::WebView(format!("SetParent failed: {err}")))?;
}
self.parent = parent;
let _ = WindowsAndMessaging::SetWindowPos(
self.hwnd,
Some(WindowsAndMessaging::HWND_BOTTOM),
0,
0,
0,
0,
WindowsAndMessaging::SWP_NOMOVE
| WindowsAndMessaging::SWP_NOSIZE
| WindowsAndMessaging::SWP_NOACTIVATE,
);
}
Ok(())
}
pub(crate) fn destroy(&self) {
unsafe {
let _ = WindowsAndMessaging::DestroyWindow(self.hwnd);
}
}
}