mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-05 10:13:00 +02:00
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:
File diff suppressed because it is too large
Load Diff
100
crates/tauri-runtime-cef/src/cef_webview.rs
Normal file
100
crates/tauri-runtime-cef/src/cef_webview.rs
Normal 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>;
|
||||
}
|
||||
118
crates/tauri-runtime-cef/src/cef_webview/macos.rs
Normal file
118
crates/tauri-runtime-cef/src/cef_webview/macos.rs
Normal 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) };
|
||||
}
|
||||
}
|
||||
186
crates/tauri-runtime-cef/src/cef_webview/windows.rs
Normal file
186
crates/tauri-runtime-cef/src/cef_webview/windows.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user