feat: check if webview runtime is accessible when creating a webview (#13406)

This commit is contained in:
Amr Bashir 2025-05-10 06:00:09 +03:00 committed by GitHub
parent 0e616dbbcb
commit 96ecfca428
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 169 additions and 0 deletions

View File

@ -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.

View File

@ -76,3 +76,4 @@ objc-exception = []
tracing = ["dep:tracing", "wry/tracing"]
macos-proxy = ["wry/mac-proxy"]
unstable = []
common-controls-v6 = []

View File

@ -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<S: AsRef<str>>(err: S) {
#[cfg(windows)]
windows::error(err);
#[cfg(not(windows))]
{
unimplemented!("Error dialog is not implemented for this platform");
}
}

View File

@ -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<S: AsRef<str>>(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::<TASKDIALOGCONFIG>() 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 <A href=\"some link\">test</A> string.";
let expected = "This is a test string.";
let result = remove_hyperlink(input);
assert_eq!(result, expected);
}
}

View File

@ -130,6 +130,8 @@ use std::{
pub type WebviewId = u32;
type IpcHandler = dyn Fn(Request<String>) + 'static;
#[cfg(not(debug_assertions))]
mod dialog;
mod monitor;
#[cfg(any(
windows,
@ -248,6 +250,7 @@ pub struct Context<T: UserEvent> {
next_webview_id: Arc<AtomicU32>,
next_window_event_id: Arc<AtomicU32>,
next_webview_event_id: Arc<AtomicU32>,
webview_runtime_installed: bool,
}
impl<T: UserEvent> Context<T> {
@ -2712,6 +2715,7 @@ impl<T: UserEvent> Wry<T> {
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<T: UserEvent>(
pending: PendingWebview<T, Wry<T>>,
#[allow(unused_variables)] focused_webview: Arc<Mutex<Option<String>>>,
) -> Result<WebviewWrapper> {
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 <A href="https://developer.microsoft.com/en-us/microsoft-edge/webview2">https://developer.microsoft.com/en-us/microsoft-edge/webview2</A>
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,

View File

@ -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.

View File

@ -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"]