refactor(cef): use native subviews instead of CEF's overlay (#14642)

* refactor(cef): use native subviews instead of CEF's overlay

* add windows implementation
This commit is contained in:
Amr Bashir
2025-12-11 15:43:18 +02:00
committed by GitHub
parent 0651215ef5
commit 67fa548168
5 changed files with 647 additions and 505 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,100 @@
use cef::*;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(windows)]
mod windows;
#[derive(Clone)]
pub enum CefWebview {
BrowserView(cef::BrowserView),
Browser(cef::Browser),
}
impl CefWebview {
pub fn is_browser(&self) -> bool {
matches!(self, CefWebview::Browser(_))
}
pub fn browser(&self) -> Option<cef::Browser> {
match self {
CefWebview::BrowserView(view) => view.browser(),
CefWebview::Browser(browser) => Some(browser.clone()),
}
}
pub fn browser_id(&self) -> i32 {
match self {
CefWebview::BrowserView(view) => view.browser().map_or(-1, |b| b.identifier()),
CefWebview::Browser(browser) => browser.identifier(),
}
}
pub fn bounds(&self) -> cef::Rect {
match self {
CefWebview::BrowserView(view) => view.bounds(),
CefWebview::Browser(browser) => browser.bounds(),
}
}
pub fn set_bounds(&self, rect: Option<&cef::Rect>) {
match self {
CefWebview::BrowserView(view) => view.set_bounds(rect),
CefWebview::Browser(browser) => browser.set_bounds(rect),
}
}
pub fn scale_factor(&self) -> f64 {
match self {
CefWebview::BrowserView(view) => view
.window()
.and_then(|w| w.display())
.map_or(1.0, |d| d.device_scale_factor() as f64),
CefWebview::Browser(browser) => browser.scale_factor(),
}
}
pub fn set_background_color(&self, color: cef::Color) {
match self {
CefWebview::BrowserView(view) => view.set_background_color(color),
CefWebview::Browser(browser) => browser.set_background_color(color),
}
}
pub fn set_visible(&self, visible: i32) {
match self {
CefWebview::BrowserView(view) => view.set_visible(visible),
CefWebview::Browser(browser) => browser.set_visible(visible),
}
}
pub fn close(&self) {
match self {
CefWebview::BrowserView(_) => {}
CefWebview::Browser(browser) => browser.close(),
}
}
pub fn set_parent(&self, parent: &cef::Window) {
match self {
CefWebview::BrowserView(_) => {}
CefWebview::Browser(browser) => browser.set_parent(parent),
}
}
}
trait CefBrowserExt {
fn bounds(&self) -> cef::Rect;
fn set_bounds(&self, rect: Option<&cef::Rect>);
fn scale_factor(&self) -> f64;
fn set_background_color(&self, color: cef::Color);
fn set_visible(&self, visible: i32);
fn close(&self);
fn set_parent(&self, parent: &cef::Window);
#[cfg(target_os = "macos")]
fn nsview(&self) -> Option<objc2::rc::Retained<objc2_app_kit::NSView>>;
#[cfg(windows)]
fn hwnd(&self) -> Option<::windows::Win32::Foundation::HWND>;
}

View File

@@ -0,0 +1,118 @@
use crate::cef_webview::CefBrowserExt;
use cef::*;
use objc2::{msg_send, rc::Retained};
use objc2_app_kit::{NSColor, NSView};
use objc2_foundation::{NSPoint, NSRect, NSSize};
impl CefBrowserExt for cef::Browser {
fn nsview(&self) -> Option<objc2::rc::Retained<objc2_app_kit::NSView>> {
let host = self.host()?;
let nsview = host.window_handle() as *mut NSView;
unsafe { Retained::<NSView>::retain(nsview) }
}
fn bounds(&self) -> cef::Rect {
let Some(nsview) = self.nsview() else {
return cef::Rect::default();
};
let parent = unsafe { nsview.superview().unwrap() };
let parent_frame = parent.frame();
let webview_frame = nsview.frame();
cef::Rect {
x: webview_frame.origin.x as i32,
y: (parent_frame.size.height - webview_frame.origin.y - webview_frame.size.height) as i32,
width: webview_frame.size.width as i32,
height: webview_frame.size.height as i32,
}
}
fn set_bounds(&self, rect: Option<&cef::Rect>) {
let Some(rect) = rect else {
return;
};
let Some(nsview) = self.nsview() else {
return;
};
let parent = unsafe { nsview.superview().unwrap() };
let parent_frame = parent.frame();
let origin = NSPoint {
x: rect.x as f64,
y: (parent_frame.size.height as f64 - (rect.y as f64 + rect.height as f64)),
};
let size = NSSize {
width: rect.width as f64,
height: rect.height as f64,
};
unsafe { nsview.setFrame(NSRect { origin, size }) };
}
fn scale_factor(&self) -> f64 {
let Some(nsview) = self.nsview() else {
return 1.0;
};
let screen = nsview.window().and_then(|w| w.screen());
screen.map(|s| s.backingScaleFactor() as f64).unwrap_or(1.0)
}
fn set_background_color(&self, color: cef::Color) {
let Some(nsview) = self.nsview() else {
return;
};
let red = ((color >> 16) & 0xFF) as f64 / 255.0;
let green = ((color >> 8) & 0xFF) as f64 / 255.0;
let blue = (color & 0xFF) as f64 / 255.0;
let alpha = ((color >> 24) & 0xFF) as f64 / 255.0;
let color = unsafe { NSColor::colorWithRed_green_blue_alpha(red, green, blue, alpha) };
let color = unsafe { color.CGColor() };
nsview.setWantsLayer(true);
let Some(layer) = (unsafe { nsview.layer() }) else {
return;
};
let _: () = unsafe { msg_send![&layer, setBackgroundColor: &*color] };
}
fn set_visible(&self, visible: i32) {
let Some(nsview) = self.nsview() else {
return;
};
if visible != 0 {
nsview.setHidden(false);
} else {
nsview.setHidden(true);
}
}
fn close(&self) {
let Some(nsview) = self.nsview() else {
return;
};
unsafe { nsview.removeFromSuperview() };
}
fn set_parent(&self, parent: &cef::Window) {
let Some(nsview) = self.nsview() else {
return;
};
let parent_nsview = parent.window_handle();
let Some(parent_nsview) = (unsafe { Retained::<NSView>::retain(parent_nsview as _) }) else {
return;
};
unsafe { parent_nsview.addSubview(&nsview) };
}
}

View File

@@ -0,0 +1,186 @@
use cef::*;
use std::sync::LazyLock;
use crate::cef_webview::CefBrowserExt;
use windows::{
core::{HRESULT, HSTRING, PCSTR},
Win32::{
Foundation::*,
Graphics::Gdi::*,
System::LibraryLoader::*,
UI::{HiDpi::*, WindowsAndMessaging::*},
},
};
impl CefBrowserExt for cef::Browser {
fn hwnd(&self) -> Option<HWND> {
let host = self.host()?;
let hwnd = host.window_handle();
Some(HWND(hwnd.0 as _))
}
fn bounds(&self) -> cef::Rect {
let Some(hwnd) = self.hwnd() else {
return cef::Rect::default();
};
let mut rect = RECT::default();
let _ = unsafe { GetClientRect(hwnd, &mut rect) };
let position_point = &mut [POINT {
x: rect.left,
y: rect.top,
}];
unsafe { MapWindowPoints(Some(hwnd), GetParent(hwnd).ok(), position_point) };
cef::Rect {
x: position_point[0].x,
y: position_point[0].y,
width: (rect.right - rect.left) as i32,
height: (rect.bottom - rect.top) as i32,
}
}
fn set_bounds(&self, rect: Option<&cef::Rect>) {
let Some(rect) = rect else {
return;
};
let Some(hwnd) = self.hwnd() else {
return;
};
let _ = unsafe {
SetWindowPos(
hwnd,
None,
rect.x,
rect.y,
rect.width,
rect.height,
SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOZORDER,
)
};
}
fn scale_factor(&self) -> f64 {
let Some(hwnd) = self.hwnd() else {
return 1.0;
};
let dpi = unsafe { hwnd_dpi(hwnd) };
dpi_to_scale_factor(dpi)
}
fn set_background_color(&self, color: cef::Color) {
// TODO:
}
fn set_visible(&self, visible: i32) {
let Some(hwnd) = self.hwnd() else {
return;
};
let cmd = if visible != 0 { SW_SHOW } else { SW_HIDE };
let _ = unsafe { ShowWindow(hwnd, cmd) };
}
fn close(&self) {
let Some(hwnd) = self.hwnd() else {
return;
};
let _ = unsafe { DestroyWindow(hwnd) };
}
fn set_parent(&self, parent: &cef::Window) {
let Some(hwnd) = self.hwnd() else {
return;
};
let parent_hwnd = HWND(parent.window_handle().0 as _);
let _ = unsafe { SetParent(hwnd, Some(parent_hwnd)) };
}
}
fn get_function_impl(library: &str, function: &str) -> FARPROC {
let library = HSTRING::from(library);
assert_eq!(function.chars().last(), Some('\0'));
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
let module = unsafe { LoadLibraryW(&library) }.unwrap_or_default();
if module.is_invalid() {
return None;
}
unsafe { GetProcAddress(module, PCSTR::from_raw(function.as_ptr())) }
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
get_function_impl($lib, concat!(stringify!($func), '\0'))
.map(|f| unsafe { std::mem::transmute::<_, $func>(f) })
};
}
pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32;
pub type GetDpiForMonitor = unsafe extern "system" fn(
hmonitor: HMONITOR,
dpi_type: MONITOR_DPI_TYPE,
dpi_x: *mut u32,
dpi_y: *mut u32,
) -> HRESULT;
static GET_DPI_FOR_WINDOW: LazyLock<Option<GetDpiForWindow>> =
LazyLock::new(|| get_function!("user32.dll", GetDpiForWindow));
static GET_DPI_FOR_MONITOR: LazyLock<Option<GetDpiForMonitor>> =
LazyLock::new(|| get_function!("shcore.dll", GetDpiForMonitor));
pub const BASE_DPI: u32 = 96;
pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
dpi as f64 / BASE_DPI as f64
}
#[allow(non_snake_case)]
pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
// We are on Windows 10 Anniversary Update (1607) or later.
match GetDpiForWindow(hwnd) {
0 => BASE_DPI, // 0 is returned if hwnd is invalid
#[allow(clippy::unnecessary_cast)]
dpi => dpi as u32,
}
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
// We are on Windows 8.1 or later.
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if monitor.is_invalid() {
return BASE_DPI;
}
let mut dpi_x = 0;
let mut dpi_y = 0;
#[allow(clippy::unnecessary_cast)]
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
dpi_x as u32
} else {
BASE_DPI
}
} else {
let hdc = GetDC(Some(hwnd));
if hdc.is_invalid() {
return BASE_DPI;
}
// We are on Vista or later.
if IsProcessDPIAware().as_bool() {
// If the process is DPI aware, then scaling must be handled by the application using
// this DPI value.
GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32
} else {
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
// application and the WM.
BASE_DPI
}
}
}

View File

@@ -42,8 +42,10 @@ use std::{
#[cfg(target_os = "macos")]
use crate::application::AppDelegateEvent;
use crate::cef_webview::CefWebview;
mod cef_impl;
mod cef_webview;
#[macro_export]
macro_rules! getter {
@@ -244,11 +246,10 @@ impl<T: UserEvent> Clone for Message<T> {
pub(crate) struct AppWebview {
pub webview_id: u32,
pub label: String,
pub browser_view: Option<cef::BrowserView>,
pub inner: CefWebview,
// browser_view.browser is null on the scheme handler factory,
// so we need to use the browser_id to identify the browser
pub browser_id: Arc<RefCell<i32>>,
pub overlay: Option<cef::OverlayController>,
pub bounds: Arc<Mutex<Option<WebviewBounds>>>,
pub devtools_enabled: bool,
pub uri_scheme_protocols: