(chore): update codebase

This commit is contained in:
Alois 2026-05-01 19:57:03 +02:00
commit 4e8d46b20e
106 changed files with 29936 additions and 1023 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,82 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use cef::{rc::*, *};
use std::sync::{Arc, Mutex, mpsc::Sender};
cef::wrap_cookie_visitor! {
pub struct CollectUrlCookiesVisitor {
pub tx: Sender<tauri_runtime::Result<Vec<tauri_runtime::Cookie<'static>>>>,
pub collected: Arc<Mutex<Vec<tauri_runtime::Cookie<'static>>>>,
}
impl CookieVisitor {
fn visit(
&self,
cookie: Option<&cef::Cookie>,
count: ::std::os::raw::c_int,
total: ::std::os::raw::c_int,
_delete_cookie: Option<&mut ::std::os::raw::c_int>,
) -> ::std::os::raw::c_int {
if let Some(c) = cookie {
let name = c.name.to_string();
let value = c.value.to_string();
let domain = c.domain.to_string();
let path = c.path.to_string();
let mut builder = tauri_runtime::Cookie::build((name, value));
if !domain.is_empty() { builder = builder.domain(domain); }
if !path.is_empty() { builder = builder.path(path); }
if c.secure == 1 { builder = builder.secure(true); }
if c.httponly == 1 { builder = builder.http_only(true); }
let ck = builder.build();
self.collected.lock().unwrap().push(ck.into_owned());
}
if (count + 1) >= total {
let _ = self.tx.send(Ok(self.collected.lock().unwrap().clone()));
}
1
}
}
}
cef::wrap_cookie_visitor! {
pub struct CollectAllCookiesVisitor {
pub tx: Sender<tauri_runtime::Result<Vec<tauri_runtime::Cookie<'static>>>>,
pub collected: Arc<Mutex<Vec<tauri_runtime::Cookie<'static>>>>,
}
impl CookieVisitor {
fn visit(
&self,
cookie: Option<&cef::Cookie>,
count: ::std::os::raw::c_int,
total: ::std::os::raw::c_int,
_delete_cookie: Option<&mut ::std::os::raw::c_int>,
) -> ::std::os::raw::c_int {
if let Some(c) = cookie {
let name = c.name.to_string();
let value = c.value.to_string();
let domain = c.domain.to_string();
let path = c.path.to_string();
let mut builder = tauri_runtime::Cookie::build((name, value));
if !domain.is_empty() { builder = builder.domain(domain); }
if !path.is_empty() { builder = builder.path(path); }
if c.secure == 1 { builder = builder.secure(true); }
if c.httponly == 1 { builder = builder.http_only(true); }
let ck = builder.build();
self.collected.lock().unwrap().push(ck.into_owned());
}
if (count + 1) >= total {
let _ = self.tx.send(Ok(self.collected.lock().unwrap().clone()));
}
1
}
}
}

View file

@ -0,0 +1,72 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#[cfg(windows)]
pub mod windows {
use cef::*;
use windows::Win32::Foundation::*;
use windows::Win32::UI::WindowsAndMessaging::*;
use windows::core::{PCWSTR, w};
/// Same as [WNDPROC] but without the Option wrapper.
type WindowProc = unsafe extern "system" fn(HWND, u32, WPARAM, LPARAM) -> LRESULT;
const ORIGINAL_WND_PROP: PCWSTR = w!("TAURI_CEF_ORIGINAL_WND_PROC");
/// Subclasses the given window to handle draggable regions
/// by replacing its window procedure with `root_window_proc`
/// and storing the original procedure as a property to be called later.
pub fn subclass_window_for_dragging(window: &mut cef::Window) {
let hwnd = window.window_handle();
let hwnd = HWND(hwnd.0 as _);
subclass_window(hwnd, root_window_proc);
}
/// Subclasses a window by replacing its window procedure with the given `proc`
/// and storing the original procedure as a property for later use.
fn subclass_window(hwnd: HWND, proc: WindowProc) {
// If already subclassed, return early
let orginial_wnd_proc = unsafe { GetPropW(hwnd, ORIGINAL_WND_PROP) };
if !orginial_wnd_proc.is_invalid() {
return;
}
// Reset last error
unsafe { SetLastError(ERROR_SUCCESS) };
// Set the new window procedure and get the orginal one
let original_wnd_proc = unsafe { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, proc as isize) };
if original_wnd_proc == 0 && unsafe { GetLastError() } != ERROR_SUCCESS {
return;
}
unsafe {
// Store the original window proc as a property for later use
let _ = SetPropW(
hwnd,
ORIGINAL_WND_PROP,
Some(HANDLE(original_wnd_proc as _)),
);
}
}
/// The root window procedure to handle WM_NCLBUTTONDOWN
/// by calling DefWindowProcW directly to allow dragging
/// and forwarding other messages to the original CEF window procedure.
unsafe extern "system" fn root_window_proc(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
if msg == WM_NCLBUTTONDOWN {
return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
}
// For other messages, call the original CEF window procedure
let original_wnd_proc = GetPropW(hwnd, ORIGINAL_WND_PROP);
let original_wnd_proc = std::mem::transmute::<_, WindowProc>(original_wnd_proc.0);
CallWindowProcW(Some(original_wnd_proc), hwnd, msg, wparam, lparam)
}
}

View file

@ -0,0 +1,432 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
borrow::Cow,
io::{Cursor, Read},
sync::Arc,
};
use cef::{rc::*, *};
use dioxus_debug_cell::RefCell;
use html5ever::{LocalName, interface::QualName, namespace_url, ns};
use http::{
HeaderMap, HeaderName, HeaderValue,
header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE},
};
use kuchiki::NodeRef;
use tauri_runtime::webview::UriSchemeProtocolHandler;
use tauri_utils::{
config::{Csp, CspDirectiveSources},
html::{parse as parse_html, serialize_node},
};
use url::Url;
use super::CefInitScript;
type HttpResponse = Arc<RefCell<Option<http::Response<Cursor<Vec<u8>>>>>>;
fn csp_inject_initialization_scripts_hashes(
existing_csp: String,
initialization_scripts: &[CefInitScript],
) -> String {
if initialization_scripts.is_empty() {
return existing_csp;
}
// For custom schemes, include ALL script hashes (we inject all scripts into HTML)
// This matches the HTML injection behavior in inject_scripts_into_html_body
let script_hashes: Vec<String> = initialization_scripts
.iter()
.map(|s| s.hash.clone())
.collect();
if script_hashes.is_empty() {
return existing_csp;
}
// Parse CSP using tauri-utils
let mut csp_map: std::collections::HashMap<String, CspDirectiveSources> =
Csp::Policy(existing_csp.to_string()).into();
// Update or create script-src directive with script hashes
let script_src = csp_map
.entry("script-src".to_string())
.or_insert_with(|| CspDirectiveSources::List(vec!["'self'".to_string()]));
// Extend with script hashes
script_src.extend(script_hashes);
// Convert back to CSP string
Csp::DirectiveMap(csp_map).to_string()
}
/// Helper function to inject initialization scripts into HTML body
fn inject_scripts_into_html_body(
body: &[u8],
initialization_scripts: &[CefInitScript],
) -> Option<Vec<u8>> {
// Check if body is valid UTF-8 HTML
let Ok(body_str) = std::str::from_utf8(body) else {
return None;
};
// Parse HTML and inject scripts
let document = parse_html(body_str.to_string());
let head = if let Ok(ref head_node) = document.select_first("head") {
head_node.as_node().clone()
} else {
let head_node = NodeRef::new_element(
QualName::new(None, ns!(html), LocalName::from("head")),
None,
);
document.prepend(head_node.clone());
head_node
};
// Inject initialization scripts (for custom schemes, inject all scripts)
for init_script in initialization_scripts.iter().rev() {
let script_el = NodeRef::new_element(QualName::new(None, ns!(html), "script".into()), None);
script_el.append(NodeRef::new_text(init_script.script.script.as_str()));
head.prepend(script_el);
}
// Serialize the modified HTML
Some(serialize_node(&document))
}
wrap_resource_request_handler! {
pub struct WebResourceRequestHandler {
initialization_scripts: Arc<Vec<CefInitScript>>,
}
impl ResourceRequestHandler {
fn on_before_resource_load(
&self,
_browser: Option<&mut Browser>,
_frame: Option<&mut Frame>,
_request: Option<&mut Request>,
_callback: Option<&mut Callback>,
) -> ReturnValue {
sys::cef_return_value_t::RV_CONTINUE.into()
}
}
}
wrap_request_handler! {
pub struct WebRequestHandler {
initialization_scripts: Arc<Vec<CefInitScript>>,
navigation_handler: Option<Arc<tauri_runtime::webview::NavigationHandler>>,
}
impl RequestHandler {
fn on_before_browse(
&self,
_browser: Option<&mut Browser>,
frame: Option<&mut Frame>,
request: Option<&mut Request>,
_user_gesture: ::std::os::raw::c_int,
_is_redirect: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int {
let Some(frame) = frame else {
return 0;
};
// we only fire main frame navigation events to match the behavior of the wry runtime
if frame.is_main() == 0 {
return 0;
}
let Some(handler) = &self.navigation_handler else {
return 0;
};
let Some(request) = request else {
return 0;
};
let url_str = CefString::from(&request.url()).to_string();
let Ok(url) = url::Url::parse(&url_str) else {
return 0;
};
let should_navigate = handler(&url);
if should_navigate {
0
} else {
1
}
}
fn resource_request_handler(
&self,
_browser: Option<&mut Browser>,
_frame: Option<&mut Frame>,
_request: Option<&mut Request>,
_is_navigation: ::std::os::raw::c_int,
_is_download: ::std::os::raw::c_int,
_request_initiator: Option<&CefString>,
_disable_default_handling: Option<&mut ::std::os::raw::c_int>,
) -> Option<ResourceRequestHandler> {
Some(WebResourceRequestHandler::new(
self.initialization_scripts.clone(),
))
}
}
}
wrap_resource_handler! {
pub struct WebResourceHandler {
webview_label: String,
handler: Arc<Box<UriSchemeProtocolHandler>>,
initialization_scripts: Arc<Vec<CefInitScript>>,
// we clone response to send it to the handler thread
response: HttpResponse,
}
impl ResourceHandler {
fn process_request(
&self,
request: Option<&mut Request>,
callback: Option<&mut Callback>,
) -> ::std::os::raw::c_int {
let Some(request) = request else { return 0 };
let Some(callback) = callback else { return 0 };
let url = CefString::from(&request.url()).to_string();
let url = Url::parse(&url).ok();
if let Some(url) = url {
let callback = ThreadSafe(callback.clone());
let response_store = ThreadSafe(self.response.clone());
let initialization_scripts = self.initialization_scripts.clone();
let responder = Box::new(move |response: http::Response<Cow<'static, [u8]>>| {
// Check if this is an HTML response that needs script injection
let content_type = response.headers().get(CONTENT_TYPE);
let is_html = content_type
.and_then(|ct| ct.to_str().ok())
.map(|ct| ct.to_lowercase().starts_with("text/html"))
.unwrap_or(false);
let (parts, body) = response.into_parts();
let body_bytes = body.into_owned();
let modified_body = if is_html {
inject_scripts_into_html_body(&body_bytes, &initialization_scripts)
.unwrap_or(body_bytes)
} else {
body_bytes
};
let mut response = http::Response::from_parts(parts, Cursor::new(modified_body));
let csp = response
.headers_mut()
.get_mut(CONTENT_SECURITY_POLICY);
if let Some(csp) = csp {
let csp_string = csp.to_str().unwrap().to_string();
let new_csp = csp_inject_initialization_scripts_hashes(
csp_string,
&initialization_scripts,
);
*csp = HeaderValue::from_str(&new_csp).unwrap();
}
response_store.into_owned().borrow_mut().replace(response);
let callback = callback.into_owned();
callback.cont();
});
let label = self.webview_label.clone();
let handler = self.handler.clone();
let data = read_request_body(request);
let headers = get_request_headers(request);
let method_str = CefString::from(&request.method()).to_string();
let method = http::Method::from_bytes(method_str.as_bytes())
.unwrap_or(http::Method::GET);
std::thread::spawn(move || {
let mut http_request = http::Request::builder().method(method).uri(url.as_str()).body(data).unwrap();
*http_request.headers_mut() = headers;
// handler is Arc<Box<UriSchemeProtocol>>, so we need to dereference to call it
(**handler)(&label, http_request, responder);
});
1
} else {
0
}
}
fn read(
&self,
data_out: *mut u8,
bytes_to_read: ::std::os::raw::c_int,
bytes_read: Option<&mut ::std::os::raw::c_int>,
_callback: Option<&mut ResourceReadCallback>,
) -> ::std::os::raw::c_int {
let Ok(bytes_to_read) = usize::try_from(bytes_to_read) else {
return 0;
};
let data_out = unsafe { std::slice::from_raw_parts_mut(data_out, bytes_to_read) };
let count = self.response.borrow_mut().as_mut().and_then(|response| response.body_mut().read(data_out).ok()).unwrap_or(0);
if let Some(bytes_read) = bytes_read {
let Ok(count) = count.try_into() else {
return 0;
};
*bytes_read = count;
if count > 0 {
return 1;
}
}
0
}
fn response_headers(
&self,
response: Option<&mut Response>,
response_length: Option<&mut i64>,
redirect_url: Option<&mut CefString>,
) {
let (Some(response), Some(response_data)) = (response, &*self.response.borrow()) else { return };
response.set_status(response_data.status().as_u16() as i32);
let mut content_type = None;
// First pass: collect CSP header and set other headers
for (name, value) in response_data.headers() {
let Ok(value) = value.to_str() else { continue; };
response.set_header_by_name(Some(&name.as_str().into()), Some(&value.into()), 0);
if name == CONTENT_TYPE {
content_type.replace(value.to_string());
}
}
response.set_header_by_name(
Some(&"Cache-Control".into()),
Some(&"no-store".into()),
1,
);
let mime_type = content_type
.as_ref()
.and_then(|t| t.split(';').next())
.map(str::trim)
.unwrap_or("text/plain");
response.set_mime_type(Some(&mime_type.into()));
if let Some(length) = response_length { *length = -1; }
if let Some(redirect_url) = redirect_url {
let _ = std::mem::take(redirect_url);
}
}
}
}
wrap_scheme_handler_factory! {
pub struct UriSchemeHandlerFactory {
registry: super::SchemeHandlerRegistry,
scheme: String,
}
impl SchemeHandlerFactory {
fn create(
&self,
browser: Option<&mut Browser>,
_frame: Option<&mut Frame>,
_scheme_name: Option<&CefString>,
_request: Option<&mut Request>,
) -> Option<ResourceHandler> {
let browser = browser?;
let id = browser.identifier();
// get handler from our regsitry based on browser ID and scheme
let (webview_label, handler, initialization_scripts) = self
.registry
.lock()
.unwrap()
.get(&(id, self.scheme.clone()))
.cloned()?;
Some(WebResourceHandler::new(webview_label, handler, initialization_scripts, Arc::new(RefCell::new(None))))
}
}
}
struct ThreadSafe<T>(T);
impl<T> ThreadSafe<T> {
fn into_owned(self) -> T {
self.0
}
}
unsafe impl<T> Send for ThreadSafe<T> {}
unsafe impl<T> Sync for ThreadSafe<T> {}
fn read_request_body(request: &mut Request) -> Vec<u8> {
let mut body = Vec::new();
if let Some(post_data) = request.post_data() {
let mut elements = vec![None; post_data.element_count()];
post_data.elements(Some(&mut elements));
for element in elements.into_iter().flatten() {
match element.get_type().as_ref() {
sys::cef_postdataelement_type_t::PDE_TYPE_BYTES => {
let size = element.bytes_count();
if size > 0 {
let mut buf = vec![0u8; size];
// Copy bytes into our buffer
let copied = element.bytes(size, buf.as_mut_ptr());
// Safety: CEF promises it wrote `copied` bytes into buf
unsafe {
buf.set_len(copied);
}
body.extend(buf);
}
}
sys::cef_postdataelement_type_t::PDE_TYPE_FILE => {
// Read file from disk
let file_path = CefString::from(&element.file()).to_string();
if let Ok(mut file) = std::fs::File::open(&file_path) {
use std::io::Read;
let mut buf = Vec::new();
if file.read_to_end(&mut buf).is_ok() {
body.extend(buf);
}
}
}
_ => {}
}
}
}
body
}
fn get_request_headers(request: &mut Request) -> HeaderMap {
let mut headers = HeaderMap::new();
let mut map = CefStringMultimap::new();
request.header_map(Some(&mut map));
// Iterate through all entries
for (name, value) in map {
for v in value {
headers.append(
HeaderName::from_bytes(name.as_bytes()).unwrap(),
HeaderValue::from_str(&v).unwrap(),
);
}
}
headers
}

View file

@ -0,0 +1,110 @@
use cef::*;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(windows)]
mod windows;
#[cfg(target_os = "linux")]
mod linux;
#[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 set_background_color(&self, color: Option<u32>) {
if let CefWebview::BrowserView(view) = self {
let window = view.window();
let color = color.or_else(|| {
window.map(|w| w.theme_color(ColorId::COLOR_PRIMARY_BACKGROUND.get_raw() as _))
});
if let Some(color) = color {
view.set_background_color(color);
}
}
}
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_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_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>;
#[cfg(target_os = "linux")]
fn xid(&self) -> Option<u64>;
}

View file

@ -0,0 +1,208 @@
use cef::*;
use std::sync::LazyLock;
use x11_dl::xlib;
use crate::cef_webview::CefBrowserExt;
static X11: LazyLock<Option<xlib::Xlib>> = LazyLock::new(|| xlib::Xlib::open().ok());
impl CefBrowserExt for cef::Browser {
fn xid(&self) -> Option<u64> {
let host = self.host()?;
let xid = host.window_handle();
Some(xid)
}
fn bounds(&self) -> cef::Rect {
let Some(xid) = self.xid() else {
return cef::Rect::default();
};
let Some(xlib) = X11.as_ref() else {
return cef::Rect::default();
};
unsafe {
let display = (xlib.XOpenDisplay)(std::ptr::null());
if display.is_null() {
return cef::Rect::default();
}
let mut root: xlib::Window = 0;
let mut x: i32 = 0;
let mut y: i32 = 0;
let mut width: u32 = 0;
let mut height: u32 = 0;
let mut border_width: u32 = 0;
let mut depth: u32 = 0;
let status = (xlib.XGetGeometry)(
display,
xid as xlib::Window,
&mut root,
&mut x,
&mut y,
&mut width,
&mut height,
&mut border_width,
&mut depth,
);
(xlib.XCloseDisplay)(display);
if status == 0 {
return cef::Rect::default();
}
// XGetGeometry returns position relative to parent, which is what we need
cef::Rect {
x,
y,
width: width as i32,
height: height as i32,
}
}
}
fn set_bounds(&self, rect: Option<&cef::Rect>) {
let Some(rect) = rect else {
return;
};
let Some(xid) = self.xid() else {
return;
};
let Some(xlib) = X11.as_ref() else {
return;
};
unsafe {
let display = (xlib.XOpenDisplay)(std::ptr::null());
if display.is_null() {
return;
}
(xlib.XMoveResizeWindow)(
display,
xid as xlib::Window,
rect.x,
rect.y,
rect.width as u32,
rect.height as u32,
);
// Ensure window is mapped and raised after setting bounds
(xlib.XMapRaised)(display, xid as xlib::Window);
(xlib.XFlush)(display);
(xlib.XCloseDisplay)(display);
}
}
fn scale_factor(&self) -> f64 {
// Get scale factor from primary display
// CEF on Linux doesn't provide direct access to the window's display,
// so we use the primary display as a reasonable default
cef::display_get_primary()
.map(|d| d.device_scale_factor() as f64)
.unwrap_or(1.0)
}
fn set_visible(&self, visible: i32) {
let Some(xid) = self.xid() else {
return;
};
let Some(xlib) = X11.as_ref() else {
return;
};
unsafe {
let display = (xlib.XOpenDisplay)(std::ptr::null());
if display.is_null() {
return;
}
if visible != 0 {
(xlib.XMapWindow)(display, xid as xlib::Window);
} else {
(xlib.XUnmapWindow)(display, xid as xlib::Window);
}
(xlib.XFlush)(display);
(xlib.XCloseDisplay)(display);
}
}
fn close(&self) {
let Some(xid) = self.xid() else {
return;
};
let Some(xlib) = X11.as_ref() else {
return;
};
unsafe {
let display = (xlib.XOpenDisplay)(std::ptr::null());
if display.is_null() {
return;
}
(xlib.XDestroyWindow)(display, xid as xlib::Window);
(xlib.XFlush)(display);
(xlib.XCloseDisplay)(display);
}
}
fn set_parent(&self, parent: &cef::Window) {
let Some(xid) = self.xid() else {
return;
};
let parent_xid = parent.window_handle();
if parent_xid == 0 {
return;
}
let Some(xlib) = X11.as_ref() else {
return;
};
unsafe {
let display = (xlib.XOpenDisplay)(std::ptr::null());
if display.is_null() {
return;
}
// Check if window exists before reparenting
let mut root: xlib::Window = 0;
let mut parent_window: xlib::Window = 0;
let mut children: *mut xlib::Window = std::ptr::null_mut();
let mut nchildren: u32 = 0;
let status = (xlib.XQueryTree)(
display,
xid as xlib::Window,
&mut root,
&mut parent_window,
&mut children,
&mut nchildren,
);
if status != 0 && !children.is_null() {
(xlib.XFree)(children as *mut std::ffi::c_void);
}
(xlib.XReparentWindow)(
display,
xid as xlib::Window,
parent_xid as xlib::Window,
0,
0,
);
// Ensure window is mapped and raised after reparenting
(xlib.XMapRaised)(display, xid as xlib::Window);
(xlib.XFlush)(display);
(xlib.XCloseDisplay)(display);
}
}
}

View file

@ -0,0 +1,99 @@
use crate::cef_webview::CefBrowserExt;
use cef::*;
use objc2::rc::Retained;
use objc2_app_kit::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 - (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()).unwrap_or(1.0)
}
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) {
crate::cef_impl::ensure_valid_content_view(parent.window_handle());
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,210 @@
use cef::*;
use std::sync::LazyLock;
use crate::cef_webview::CefBrowserExt;
use windows::{
Win32::{
Foundation::*,
Graphics::Gdi::*,
System::LibraryLoader::*,
UI::{HiDpi::*, WindowsAndMessaging::*},
},
core::{HRESULT, HSTRING, PCSTR},
};
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_visible(&self, visible: i32) {
let Some(hwnd) = self.hwnd() else {
return;
};
if visible != 0 {
let _ = unsafe { ShowWindow(hwnd, SW_SHOW) };
unsafe { ensure_render_target(hwnd) };
} else {
let _ = unsafe { ShowWindow(hwnd, SW_HIDE) };
}
}
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)) };
}
}
/// Toggle visibility on Chrome_WidgetWin_1 children to force CEF to rebind
/// the compositor surface and emit a fresh paint after the child webview has
/// been hidden.
///
/// Required when the child has been hidden, particularly when paired with CDP
/// `Page.setWebLifecycleState: frozen`, which pauses the renderer's compositor.
/// The Chrome_RenderWidgetHostHWND host window survives the freeze, but its
/// compositor surface is stale and won't repaint on its own when the parent is
/// shown again — leaving the widget visible with blank content. The hide+show
/// dance on Chrome_WidgetWin_1 forces Chromium to rebind the surface and
/// schedule a paint.
unsafe fn ensure_render_target(hwnd: HWND) {
use windows::core::PCWSTR;
const CHROME_WIDGET: PCWSTR = windows::core::w!("Chrome_WidgetWin_1");
let mut child = unsafe { FindWindowExW(Some(hwnd), None, CHROME_WIDGET, PCWSTR::null()) };
while let Ok(child_hwnd) = child {
let _ = unsafe { ShowWindow(child_hwnd, SW_HIDE) };
let _ = unsafe { ShowWindow(child_hwnd, SW_SHOW) };
child = unsafe { FindWindowExW(Some(hwnd), Some(child_hwnd), CHROME_WIDGET, PCWSTR::null()) };
}
}
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
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,42 @@
#[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<u32> {
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,
)
}
/// Adjusts the given size to account for window borders, so that the resulting inner size matches the requested size.
///
/// Expects and returns a size in physical pixels.
pub fn adjust_size(hwnd: cef::sys::HWND, size: cef::Size) -> cef::Size {
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;
cef::Size {
width: size.width + width_diff,
height: size.height + height_diff,
}
}
}