From d12b176eb3bece65ce62f645fca573ff5de318e7 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Mon, 9 Feb 2026 12:50:45 +0200 Subject: [PATCH] fix(cef): adjust inner size on Windows to account for borders (#14911) --- crates/tauri-runtime-cef/src/cef_impl.rs | 128 ++++++++++++++--------- crates/tauri-runtime-cef/src/lib.rs | 1 + crates/tauri-runtime-cef/src/utils.rs | 39 +++++++ 3 files changed, 118 insertions(+), 50 deletions(-) create mode 100644 crates/tauri-runtime-cef/src/utils.rs diff --git a/crates/tauri-runtime-cef/src/cef_impl.rs b/crates/tauri-runtime-cef/src/cef_impl.rs index 5d9476810..8beca301b 100644 --- a/crates/tauri-runtime-cef/src/cef_impl.rs +++ b/crates/tauri-runtime-cef/src/cef_impl.rs @@ -961,30 +961,6 @@ wrap_window_delegate! { cef::Size { width: 0, height: 0 } } } - - fn on_layout_changed(&self, _view: Option<&mut View>, bounds: Option<&cef::Rect>) { - let Some(bounds) = bounds else { - return; - }; - - let Ok(windows) = self.windows.try_borrow() else { return; }; - - if let Some(app_window) = windows.get(&self.window_id) { - for wrapper in &app_window.webviews { - if wrapper.inner.is_browser() { - if let Some(b) = &*wrapper.bounds.lock().unwrap() { - let new_rect = cef::Rect { - x: (bounds.width as f32 * b.x_rate) as i32, - y: (bounds.height as f32 * b.y_rate) as i32, - width: (bounds.width as f32 * b.width_rate) as i32, - height: (bounds.height as f32 * b.height_rate) as i32, - }; - wrapper.inner.set_bounds(Some(&new_rect)); - } - } - } - } - } } impl PanelDelegate {} @@ -1013,10 +989,21 @@ wrap_window_delegate! { window.set_title(Some(&CefString::from(title.as_str()))); } - if let Some(inner_size) = &a.inner_size { + if let Some(mut inner_size) = &a.inner_size { if let Some(display) = window.display() { - let device_scale_factor = display.device_scale_factor() as f64; - let logical_size = inner_size.to_logical::(device_scale_factor); + let scale = display.device_scale_factor() as f64; + + // On Windows, the size set via CEF APIs is the outer size (including borders), + // so we need to adjust it to set the correct inner size. + #[cfg(windows)] + { + let size = inner_size.to_physical::(scale); + inner_size = crate::utils::windows::adjust_size(window.window_handle(), size).into(); + } + + let logical_size = inner_size.to_logical::(scale); + + window.set_size(Some(&cef::Size { width: logical_size.width as i32, height: logical_size.height as i32, @@ -1209,16 +1196,23 @@ wrap_window_delegate! { apply_traffic_light_position(window.window_handle(), pos); } - // Update autoresize overlay bounds (moved from on_layout_changed) + #[cfg(not(windows))] + let size = LogicalSize::new(bounds.width as u32, bounds.height as u32); + + // On Windows, we need to get the inner size because the bounds include the window borders. + #[cfg(windows)] + let size = crate::utils::windows::inner_size(window.window_handle()); + + // Update autoresize overlay bounds if let Some(app_window) = self.windows.borrow().get(&self.window_id) { for wrapper in &app_window.webviews { if wrapper.inner.is_browser() { if let Some(b) = &*wrapper.bounds.lock().unwrap(){ let new_rect = cef::Rect { - x: (bounds.width as f32 * b.x_rate) as i32, - y: (bounds.height as f32 * b.y_rate) as i32, - width: (bounds.width as f32 * b.width_rate) as i32, - height: (bounds.height as f32 * b.height_rate) as i32, + x: (size.width as f32 * b.x_rate) as i32, + y: (size.height as f32 * b.y_rate) as i32, + width: (size.width as f32 * b.width_rate) as i32, + height: (size.height as f32 * b.height_rate) as i32, }; wrapper.inner.set_bounds(Some(&new_rect)); } @@ -2081,14 +2075,22 @@ fn handle_window_message( .get(&window_id) .map(|w| match &w.window { crate::AppWindowKind::Window(window) => { - let bounds = window.bounds(); - let scale = window - .display() - .map(|d| d.device_scale_factor() as f64) - .unwrap_or(1.0); - Ok( - LogicalSize::new(bounds.width as u32, bounds.height as u32).to_physical::(scale), - ) + #[cfg(not(windows))] + let size = { + let scale = window + .display() + .map(|d| d.device_scale_factor() as f64) + .unwrap_or(1.0); + + let bounds = window.bounds(); + LogicalSize::new(bounds.width as u32, bounds.height as u32).to_physical::(scale) + }; + + // On Windows, window.bounds() is the outer size, not the inner size. + #[cfg(windows)] + let size = crate::utils::windows::inner_size(window.window_handle()); + + Ok(size) } crate::AppWindowKind::BrowserWindow => Err(tauri_runtime::Error::FailedToSendMessage), }) @@ -2485,12 +2487,21 @@ fn handle_window_message( } } } - WindowMessage::SetSize(size) => { + WindowMessage::SetSize(mut size) => { if let Some(app_window) = context.windows.borrow().get(&window_id) { if let Some(window) = app_window.window() { if let Some(display) = window.display() { let device_scale_factor = display.device_scale_factor() as f64; - let logical_size = size.to_logical::(device_scale_factor); + + // On Windows, the size set via CEF APIs is the outer size (including borders), + // so we need to adjust it to set the correct inner size. + #[cfg(windows)] + { + let inner_size = size.to_physical::(device_scale_factor); + size = crate::utils::windows::adjust_size(window.window_handle(), inner_size).into(); + } + + let logical_size = size.to_logical::(device_scale_factor); window.set_size(Some(&cef::Size { width: logical_size.width as i32, height: logical_size.height as i32, @@ -3162,8 +3173,20 @@ pub(crate) fn create_webview( .display() .map(|d| d.device_scale_factor() as f64) .unwrap_or(1.0); + + // On Windows, CEF expects physical coordinates for child windows. + #[cfg(windows)] + let logical_position = bounds.position.to_physical::(device_scale_factor); + #[cfg(windows)] + let logical_size = bounds.size.to_physical::(device_scale_factor); + + dbg!(logical_size); + + #[cfg(not(windows))] let logical_position = bounds.position.to_logical::(device_scale_factor); + #[cfg(not(windows))] let logical_size = bounds.size.to_logical::(device_scale_factor); + cef::Rect { x: logical_position.x, y: logical_position.y, @@ -3336,18 +3359,23 @@ fn webview_bounds_ratio( webview_bounds: Option, browser: &CefWebview, ) -> crate::WebviewBounds { - let window_bounds = window.bounds(); - let window_size = LogicalSize::new(window_bounds.width as u32, window_bounds.height as u32); + #[cfg(not(windows))] + let window_size = { + let window_bounds = window.bounds(); + LogicalSize::new(window_bounds.width as u32, window_bounds.height as u32) + }; + + // On Windows, CEF's window bounds is the outer size not the inner size. + #[cfg(windows)] + let window_size = crate::utils::windows::inner_size(window.window_handle()); let ob = webview_bounds.unwrap_or_else(|| browser.bounds()); - let pos = LogicalPosition::new(ob.x, ob.y); - let size = LogicalSize::new(ob.width as u32, ob.height as u32); crate::WebviewBounds { - x_rate: pos.x as f32 / window_size.width as f32, - y_rate: pos.y as f32 / window_size.height as f32, - width_rate: size.width as f32 / window_size.width as f32, - height_rate: size.height as f32 / window_size.height as f32, + x_rate: ob.x as f32 / window_size.width as f32, + y_rate: ob.y as f32 / window_size.height as f32, + width_rate: ob.width as f32 / window_size.width as f32, + height_rate: ob.height as f32 / window_size.height as f32, } } diff --git a/crates/tauri-runtime-cef/src/lib.rs b/crates/tauri-runtime-cef/src/lib.rs index 49140366b..32719aa78 100644 --- a/crates/tauri-runtime-cef/src/lib.rs +++ b/crates/tauri-runtime-cef/src/lib.rs @@ -46,6 +46,7 @@ use crate::cef_webview::CefWebview; mod cef_impl; mod cef_webview; +mod utils; #[macro_export] macro_rules! getter { diff --git a/crates/tauri-runtime-cef/src/utils.rs b/crates/tauri-runtime-cef/src/utils.rs new file mode 100644 index 000000000..0326c7402 --- /dev/null +++ b/crates/tauri-runtime-cef/src/utils.rs @@ -0,0 +1,39 @@ +#[cfg(windows)] +pub mod windows { + use tauri_runtime::dpi::PhysicalSize; + use windows::Win32::Foundation::*; + use windows::Win32::UI::WindowsAndMessaging::*; + + pub fn inner_size(hwnd: cef::sys::HWND) -> PhysicalSize { + let hwnd = HWND(hwnd.0 as _); + let mut rect = RECT::default(); + let _ = unsafe { GetClientRect(hwnd, &mut rect) }; + + PhysicalSize::new( + (rect.right - rect.left) as u32, + (rect.bottom - rect.top) as u32, + ) + } + + pub fn adjust_size(hwnd: cef::sys::HWND, size: PhysicalSize) -> PhysicalSize { + let hwnd = HWND(hwnd.0 as _); + + let mut client_rect = RECT::default(); + let _ = unsafe { GetClientRect(hwnd, &mut client_rect) }; + let client_width = client_rect.right - client_rect.left; + let client_height = client_rect.bottom - client_rect.top; + + let mut window_rect = RECT::default(); + let _ = unsafe { GetWindowRect(hwnd, &mut window_rect) }; + let window_width = window_rect.right - window_rect.left; + let window_height = window_rect.bottom - window_rect.top; + + let width_diff = window_width - client_width; + let height_diff = window_height - client_height; + + PhysicalSize::new( + size.width + width_diff as u32, + size.height + height_diff as u32, + ) + } +}