From 96ecfca428e4e5d9ff5d5eeed3f94a06a466ed02 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Sat, 10 May 2025 06:00:09 +0300 Subject: [PATCH] feat: check if webview runtime is accessible when creating a webview (#13406) --- .changes/webview-runtime-check.md | 7 + crates/tauri-runtime-wry/Cargo.toml | 1 + crates/tauri-runtime-wry/src/dialog/mod.rs | 16 +++ .../tauri-runtime-wry/src/dialog/windows.rs | 124 ++++++++++++++++++ crates/tauri-runtime-wry/src/lib.rs | 18 +++ crates/tauri-runtime/src/lib.rs | 2 + crates/tauri/Cargo.toml | 1 + 7 files changed, 169 insertions(+) create mode 100644 .changes/webview-runtime-check.md create mode 100644 crates/tauri-runtime-wry/src/dialog/mod.rs create mode 100644 crates/tauri-runtime-wry/src/dialog/windows.rs diff --git a/.changes/webview-runtime-check.md b/.changes/webview-runtime-check.md new file mode 100644 index 000000000..823d50f1b --- /dev/null +++ b/.changes/webview-runtime-check.md @@ -0,0 +1,7 @@ +--- +"tauri": "patch:enhance" +"tauri-runtime-wry": "patch:enhance" +--- + +Check if the webview runtime is accessible when creating a webview, returning an error if it doesn't. + diff --git a/crates/tauri-runtime-wry/Cargo.toml b/crates/tauri-runtime-wry/Cargo.toml index 687c3d531..6678ec814 100644 --- a/crates/tauri-runtime-wry/Cargo.toml +++ b/crates/tauri-runtime-wry/Cargo.toml @@ -76,3 +76,4 @@ objc-exception = [] tracing = ["dep:tracing", "wry/tracing"] macos-proxy = ["wry/mac-proxy"] unstable = [] +common-controls-v6 = [] diff --git a/crates/tauri-runtime-wry/src/dialog/mod.rs b/crates/tauri-runtime-wry/src/dialog/mod.rs new file mode 100644 index 000000000..156d6a6d2 --- /dev/null +++ b/crates/tauri-runtime-wry/src/dialog/mod.rs @@ -0,0 +1,16 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[cfg(windows)] +mod windows; + +pub fn error>(err: S) { + #[cfg(windows)] + windows::error(err); + + #[cfg(not(windows))] + { + unimplemented!("Error dialog is not implemented for this platform"); + } +} diff --git a/crates/tauri-runtime-wry/src/dialog/windows.rs b/crates/tauri-runtime-wry/src/dialog/windows.rs new file mode 100644 index 000000000..137371ef9 --- /dev/null +++ b/crates/tauri-runtime-wry/src/dialog/windows.rs @@ -0,0 +1,124 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use windows::core::{w, HSTRING, PCWSTR}; + +enum Level { + Error, + #[allow(unused)] + Warning, + #[allow(unused)] + Info, +} + +pub fn error>(err: S) { + dialog_inner(err.as_ref(), Level::Error); +} + +fn dialog_inner(err: &str, level: Level) { + let title = match level { + Level::Warning => w!("Warning"), + Level::Error => w!("Error"), + Level::Info => w!("Info"), + }; + + #[cfg(not(feature = "common-controls-v6"))] + { + use windows::Win32::UI::WindowsAndMessaging::*; + + let err = remove_hyperlink(err); + let err = HSTRING::from(err); + + unsafe { + MessageBoxW( + None, + err, + title, + match level { + Level::Warning => MB_ICONWARNING, + Level::Error => MB_ICONERROR, + Level::Info => MB_ICONINFORMATION, + }, + ) + }; + } + + #[cfg(feature = "common-controls-v6")] + { + use windows::core::HRESULT; + use windows::Win32::Foundation::*; + use windows::Win32::UI::Controls::*; + use windows::Win32::UI::Shell::*; + use windows::Win32::UI::WindowsAndMessaging::*; + + extern "system" fn task_dialog_callback( + _hwnd: HWND, + msg: TASKDIALOG_NOTIFICATIONS, + _wparam: WPARAM, + lparam: LPARAM, + _data: isize, + ) -> HRESULT { + if msg == TDN_HYPERLINK_CLICKED { + let link = PCWSTR(lparam.0 as _); + let _ = unsafe { ShellExecuteW(None, None, link, None, None, SW_SHOWNORMAL) }; + } + + S_OK + } + + let err = HSTRING::from(err); + let err = PCWSTR(err.as_ptr()); + + let task_dialog_config = TASKDIALOGCONFIG { + cbSize: std::mem::size_of::() as u32, + dwFlags: TDF_ALLOW_DIALOG_CANCELLATION | TDF_ENABLE_HYPERLINKS, + pszWindowTitle: title, + pszContent: err, + Anonymous1: TASKDIALOGCONFIG_0 { + pszMainIcon: match level { + Level::Warning => TD_WARNING_ICON, + Level::Error => TD_ERROR_ICON, + Level::Info => TD_INFORMATION_ICON, + }, + }, + dwCommonButtons: TDCBF_OK_BUTTON, + pfCallback: Some(task_dialog_callback), + ..Default::default() + }; + + let _ = unsafe { TaskDialogIndirect(&task_dialog_config, None, None, None) }; + } +} + +#[cfg(not(feature = "common-controls-v6"))] +fn remove_hyperlink(str: &str) -> String { + let mut result = String::new(); + let mut in_hyperlink = false; + + for c in str.chars() { + if c == '<' { + in_hyperlink = true; + } else if c == '>' { + in_hyperlink = false; + } else if !in_hyperlink { + result.push(c); + } + } + + result +} + +#[cfg(test)] +#[cfg(not(feature = "common-controls-v6"))] +mod tests { + use super::*; + + #[test] + fn test_remove_hyperlink() { + let input = "This is a test string."; + let expected = "This is a test string."; + let result = remove_hyperlink(input); + assert_eq!(result, expected); + } +} diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index ac5544a06..35cb0eea3 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -130,6 +130,8 @@ use std::{ pub type WebviewId = u32; type IpcHandler = dyn Fn(Request) + 'static; +#[cfg(not(debug_assertions))] +mod dialog; mod monitor; #[cfg(any( windows, @@ -248,6 +250,7 @@ pub struct Context { next_webview_id: Arc, next_window_event_id: Arc, next_webview_event_id: Arc, + webview_runtime_installed: bool, } impl Context { @@ -2712,6 +2715,7 @@ impl Wry { next_webview_id: Default::default(), next_window_event_id: Default::default(), next_webview_event_id: Default::default(), + webview_runtime_installed: wry::webview_version().is_ok(), }; Ok(Self { @@ -4421,6 +4425,20 @@ fn create_webview( pending: PendingWebview>, #[allow(unused_variables)] focused_webview: Arc>>, ) -> Result { + if !context.webview_runtime_installed { + #[cfg(all(not(debug_assertions), windows))] + dialog::error( + r#"Could not find the WebView2 Runtime. + +Make sure it is installed or download it from https://developer.microsoft.com/en-us/microsoft-edge/webview2 + +You may have it installed on another user account, but it is not available for this one. +"#, + ); + + return Err(Error::WebviewRuntimeNotInstalled); + } + #[allow(unused_mut)] let PendingWebview { webview_attributes, diff --git a/crates/tauri-runtime/src/lib.rs b/crates/tauri-runtime/src/lib.rs index 201ec65b0..546512261 100644 --- a/crates/tauri-runtime/src/lib.rs +++ b/crates/tauri-runtime/src/lib.rs @@ -169,6 +169,8 @@ pub enum Error { #[cfg(any(target_os = "macos", target_os = "ios"))] #[error("failed to remove data store")] FailedToRemoveDataStore, + #[error("Could not find the webview runtime, make sure it is installed")] + WebviewRuntimeNotInstalled, } /// Result type. diff --git a/crates/tauri/Cargo.toml b/crates/tauri/Cargo.toml index 6fb06201d..cc72f79a3 100644 --- a/crates/tauri/Cargo.toml +++ b/crates/tauri/Cargo.toml @@ -185,6 +185,7 @@ unstable = ["tauri-runtime-wry?/unstable"] common-controls-v6 = [ "tray-icon?/common-controls-v6", "muda/common-controls-v6", + "tauri-runtime-wry?/common-controls-v6", ] tray-icon = ["dep:tray-icon"] tracing = ["dep:tracing", "tauri-macros/tracing", "tauri-runtime-wry?/tracing"]