< Summary

Information
Class: /home/runner/work/TauriQt/TauriQt/tauri-runtime-qt/src/ops/webview.rs
Assembly: Default
File(s): /home/runner/work/TauriQt/TauriQt/tauri-runtime-qt/src/ops/webview.rs
Line coverage
2%
Covered lines: 4
Uncovered lines: 150
Coverable lines: 154
Total lines: 309
Line coverage: 2.5%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

File(s)

/home/runner/work/TauriQt/TauriQt/tauri-runtime-qt/src/ops/webview.rs

#LineLine coverage
 1// SPDX-FileCopyrightText: 2026 Alper Çelik
 2// SPDX-License-Identifier: MIT OR Apache-2.0
 3
 4//! Webview operations (owned by the webview worker).
 5//!
 6//! Delegates to `bridge::webview::ffi` (`qt_webview.cpp`).
 7//!
 8//! # Units
 9//!
 10//! Qt and tauri disagree on pixel units: `QWidget::geometry()` and
 11//! `QWidget::resize()/move()` are in device-independent (logical) pixels, while
 12//! tauri's `Physical*` values are device pixels. The conversion uses the
 13//! widget's `devicePixelRatioF()` (the *window's* dpr — correct under Wayland
 14//! fractional scaling, where a `QScreen`'s dpr can differ). Getters convert
 15//! Qt → physical in `qt_webview.cpp`; setters tell the C++ side whether the
 16//! caller passed logical (`Size::Logical`, used as-is) or physical values
 17//! (divided by the dpr), so the scale factor is applied in exactly one place.
 18//!
 19//! Qt widget geometry is integral *logical* pixels, so it is quantised to the
 20//! dpr grid: a physical setter followed by a physical getter can differ by up
 21//! to half a logical pixel (measured at dpr 1.5: `set_position(Physical(5, 7))`
 22//! reads back as `(5, 8)`). That is Qt's granularity, not an invented value.
 23//! Positions are relative to the window's client-area origin; sizes and
 24//! positions of a widget that has not been attached to a window yet are
 25//! relative to the screen.
 26//!
 27//! # Geometry ownership
 28//!
 29//! By default the webview is layout-managed: the window agent
 30//! (`qt_window_attach_webview`) puts the view in the window's zero-margin
 31//! `QVBoxLayout`, so it fills the client area and follows window resizes.
 32//! `set_bounds`/`set_size`/`set_position` cannot be expressed through that
 33//! layout, so the first such call detaches the view from it and Qt stops
 34//! managing its geometry: afterwards the webview no longer resizes with its
 35//! window (tauri's `set_auto_resize` is a no-op on this runtime) until it is
 36//! re-attached with `reparent`. Getters always report the live widget geometry,
 37//! whoever set it.
 38
 39use std::sync::atomic::{AtomicU64, Ordering};
 40
 41use parking_lot::Mutex;
 42use tauri_runtime::dpi::{PhysicalPosition, PhysicalSize, Position, Rect, Size};
 43use tauri_runtime::{Error, Result};
 44use tauri_runtime_qt_core as qtcore;
 45use tauri_utils::config::Color;
 46
 47use crate::bridge::webview::ffi;
 48
 049pub(crate) fn url(native: u64) -> Result<String> {
 050    Ok(ffi::qt_webview_url(native))
 051}
 52
 053pub(crate) fn navigate(native: u64, url: &str) -> Result<()> {
 054    ffi::qt_webview_navigate(native, url);
 055    Ok(())
 056}
 57
 058pub(crate) fn reload(native: u64) -> Result<()> {
 059    ffi::qt_webview_reload(native);
 060    Ok(())
 061}
 62
 063pub(crate) fn eval(native: u64, script: &str) -> Result<()> {
 064    ffi::qt_webview_eval(native, script);
 065    Ok(())
 066}
 67
 068pub(crate) fn set_zoom(native: u64, factor: f64) -> Result<()> {
 069    ffi::qt_webview_set_zoom(native, factor);
 070    Ok(())
 071}
 72
 073pub(crate) fn set_background_color(native: u64, color: Option<Color>) -> Result<()> {
 074    if let Some(Color(r, g, b, a)) = color {
 075        ffi::qt_webview_set_background_color(native, r as i32, g as i32, b as i32, a as i32);
 076    }
 077    Ok(())
 078}
 79
 80/// Places the webview at `bounds` (position + size), the same way
 81/// `set_position` + `set_size` do; a `Rect` may mix logical and physical
 82/// components, so each half is converted on its own.
 083pub(crate) fn set_bounds(native: u64, bounds: Rect) -> Result<()> {
 084    set_position(native, bounds.position)?;
 085    set_size(native, bounds.size)
 086}
 87
 088pub(crate) fn show(native: u64) -> Result<()> {
 089    ffi::qt_webview_show(native);
 090    Ok(())
 091}
 92
 093pub(crate) fn hide(native: u64) -> Result<()> {
 094    ffi::qt_webview_hide(native);
 095    Ok(())
 096}
 97
 098pub(crate) fn close(native: u64) -> Result<()> {
 099    ffi::qt_webview_close(native);
 100    // The view (and its page) is going away, so any `runJavaScript` result that
 101    // is still pending will never arrive: drop its callback instead of leaking
 102    // it for the lifetime of the process.
 0103    PENDING_EVAL
 0104        .lock()
 0105        .retain(|(_, webview, _)| *webview != native);
 0106    Ok(())
 0107}
 108
 0109pub(crate) fn print(native: u64) -> Result<()> {
 0110    ffi::qt_webview_print(native);
 0111    Ok(())
 0112}
 113
 114#[cfg(any(debug_assertions, feature = "devtools"))]
 0115pub(crate) fn open_devtools(native: u64) {
 0116    ffi::qt_webview_open_devtools(native);
 0117}
 118
 119#[cfg(any(debug_assertions, feature = "devtools"))]
 0120pub(crate) fn close_devtools(native: u64) {
 0121    ffi::qt_webview_close_devtools(native);
 0122}
 123
 124#[cfg(any(debug_assertions, feature = "devtools"))]
 0125pub(crate) fn is_devtools_open(native: u64) -> Result<bool> {
 0126    Ok(ffi::qt_webview_is_devtools_open(native))
 0127}
 128
 129// ---- geometry ----
 130
 0131pub(crate) fn bounds(native: u64) -> Result<Rect> {
 0132    let geometry = live_geometry(native)?;
 0133    Ok(qtcore::webview::geometry_rect(
 0134        geometry.x,
 0135        geometry.y,
 0136        geometry.width,
 0137        geometry.height,
 0138    ))
 0139}
 140
 0141pub(crate) fn position(native: u64) -> Result<PhysicalPosition<i32>> {
 0142    let geometry = live_geometry(native)?;
 0143    Ok(PhysicalPosition::new(geometry.x, geometry.y))
 0144}
 145
 0146pub(crate) fn size(native: u64) -> Result<PhysicalSize<u32>> {
 0147    let geometry = live_geometry(native)?;
 0148    Ok(qtcore::webview::geometry_size(
 0149        geometry.width,
 0150        geometry.height,
 0151    ))
 0152}
 153
 0154pub(crate) fn set_size(native: u64, size: Size) -> Result<()> {
 0155    let (width, height, physical) = qtcore::webview::size_params(size);
 0156    if ffi::qt_webview_set_size(native, width, height, physical) {
 0157        Ok(())
 158    } else {
 0159        Err(missing(native))
 160    }
 0161}
 162
 0163pub(crate) fn set_position(native: u64, position: Position) -> Result<()> {
 164    // A webview is a child widget, so this positions it inside its own window.
 165    // That works on Wayland too (unlike positioning a *top-level window*, which
 166    // the protocol forbids): a child's placement is client-side.
 0167    let (x, y, physical) = qtcore::webview::position_params(position);
 0168    if ffi::qt_webview_set_position(native, x, y, physical) {
 0169        Ok(())
 170    } else {
 0171        Err(missing(native))
 172    }
 0173}
 174
 0175pub(crate) fn set_focus(native: u64) -> Result<()> {
 0176    if ffi::qt_webview_set_focus(native) {
 0177        Ok(())
 178    } else {
 0179        Err(missing(native))
 180    }
 0181}
 182
 183// ---- script evaluation with a result callback ----
 184
 185/// Monotonic id for the pending-eval registry; never reused, so a late result
 186/// can never be delivered to a newer callback.
 187static NEXT_EVAL_REQUEST: AtomicU64 = AtomicU64::new(1);
 188
 189/// `(request id, webview handle, callback)` for `runJavaScript` calls whose
 190/// result has not arrived yet. `Vec` instead of a map so the whole registry can
 191/// live in a `const`-initialisable `static`.
 192static PENDING_EVAL: Mutex<Vec<(u64, u64, Box<dyn Fn(String) + Send + 'static>)>> =
 193    Mutex::new(Vec::new());
 194
 0195pub(crate) fn eval_with_callback(
 0196    native: u64,
 0197    script: &str,
 0198    callback: impl Fn(String) + Send + 'static,
 0199) -> Result<()> {
 0200    let request_id = NEXT_EVAL_REQUEST.fetch_add(1, Ordering::Relaxed);
 0201    PENDING_EVAL
 0202        .lock()
 0203        .push((request_id, native, Box::new(callback)));
 204
 0205    if ffi::qt_webview_eval_with_callback(native, request_id, script) {
 0206        Ok(())
 207    } else {
 0208        PENDING_EVAL.lock().retain(|(id, _, _)| *id != request_id);
 0209        Err(missing(native))
 210    }
 0211}
 212
 213/// Delivers a `runJavaScript` result (`json`, see the bridge) to the callback
 214/// registered for `request_id`. Called from Qt on the GUI thread.
 215///
 216/// The callback runs outside the registry lock, so a callback that calls back
 217/// into the webview API cannot deadlock; an unknown id (a result for a webview
 218/// that was closed, or a duplicate) is dropped.
 0219pub(crate) fn on_eval_result(request_id: u64, json: &str) {
 0220    let callback = {
 0221        let mut pending = PENDING_EVAL.lock();
 0222        match pending.iter().position(|(id, _, _)| *id == request_id) {
 0223            Some(index) => Some(pending.swap_remove(index).2),
 0224            None => None,
 225        }
 226    };
 227
 0228    match callback {
 0229        Some(callback) => callback(json.to_owned()),
 230        None => {
 0231            crate::qtrace!(
 232                "[tauri-runtime-qt] eval result for unknown/expired request {request_id} ({json})"
 233            );
 234        }
 235    }
 0236}
 237
 238// ---- reparenting / profile data ----
 239
 0240pub(crate) fn reparent(native: u64, window_native: u64) -> Result<()> {
 0241    if ffi::qt_webview_reparent(native, window_native) {
 0242        Ok(())
 243    } else {
 0244        Err(Error::CreateWebview(
 0245            format!(
 0246                "tauri-runtime-qt: cannot reparent webview {native} into window {window_native} \
 0247                 (unknown webview or window)"
 0248            )
 0249            .into(),
 0250        ))
 251    }
 0252}
 253
 254/// Clears the browsing data Qt 6.11 exposes: HTTP cache, cookies and visited
 255/// links; see the implementation comment in `qt_webview.cpp` for the stores it
 256/// cannot touch (localStorage, IndexedDB, service workers) — Qt offers no API
 257/// for those on a live profile. Fire-and-forget: Qt's cache clearing and cookie
 258/// deletion are asynchronous.
 0259pub(crate) fn clear_all_browsing_data(native: u64) -> Result<()> {
 0260    if ffi::qt_webview_clear_browsing_data(native) {
 0261        Ok(())
 262    } else {
 0263        Err(missing(native))
 264    }
 0265}
 266
 267// ---- diagnostics ----
 268
 269/// `TAURIQT_EVAL_DEBUG=1` smoke test for `eval_with_callback`: runs
 270/// `document.title = 'eval-ok'` and prints whatever the page reports back.
 271///
 272/// Triggered from `qt_webview.cpp` when the *first* webview finishes its first
 273/// page load. Unset (the normal case) this returns immediately, so it costs one
 274/// `getenv` per process.
 20275pub(crate) fn debug_hook_first_load(native: u64) {
 20276    if std::env::var("TAURIQT_EVAL_DEBUG").ok().as_deref() != Some("1") {
 20277        return;
 0278    }
 279
 0280    let result = eval_with_callback(native, "document.title = 'eval-ok'", |json| {
 0281        eprintln!("[tauri-runtime-qt] TAURIQT_EVAL_DEBUG eval_with_callback -> {json}");
 0282    });
 0283    match result {
 0284        Ok(()) => eprintln!(
 285            "[tauri-runtime-qt] TAURIQT_EVAL_DEBUG ran document.title = 'eval-ok' on webview {native}"
 286        ),
 0287        Err(error) => eprintln!("[tauri-runtime-qt] TAURIQT_EVAL_DEBUG failed: {error}"),
 288    }
 20289}
 290
 291// ---- helpers ----
 292
 293/// Live widget geometry in physical pixels, or `Err` for an unknown handle (Qt
 294/// cannot answer truthfully then, and a made-up `0` would be a lie).
 0295fn live_geometry(native: u64) -> Result<ffi::QtWebviewGeometry> {
 0296    let geometry = ffi::qt_webview_geometry(native);
 0297    if geometry.ok {
 0298        Ok(geometry)
 299    } else {
 0300        Err(missing(native))
 301    }
 0302}
 303
 304/// A handle that is no longer in the Qt registry. Tauri has no dedicated
 305/// "webview not found" error; `Error::CreateWebview` is this crate's webview
 306/// error carrier (see `ops`).
 0307fn missing(native: u64) -> Error {
 0308    Error::CreateWebview(format!("tauri-runtime-qt: webview {native} does not exist").into())
 0309}

Methods/Properties

_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview3url
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview8navigate
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview6reload
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview4eval
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview8set_zoom
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview20set_background_color
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview10set_bounds
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview4show
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview4hide
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview5close
_RNCNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview5close0B7_
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview5print
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview13open_devtools
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview14close_devtools
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview16is_devtools_open
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview6bounds
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview8position
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview4size
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview8set_size
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview12set_position
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview9set_focus
_RINvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview18eval_with_callbackNCNvB2_21debug_hook_first_load0EB6_
_RNCINvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview18eval_with_callbackNCNvB4_21debug_hook_first_load0E0B8_
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview14on_eval_result
_RNCNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview14on_eval_result0B7_
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview8reparent
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview23clear_all_browsing_data
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview21debug_hook_first_load
_RNCNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview21debug_hook_first_load0B7_
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview13live_geometry
_RNvNtNtCsdgr7Em4UC3i_16tauri_runtime_qt3ops7webview7missing