diff --git a/Cargo.lock b/Cargo.lock index ce4caf8fd77..aa3f9849b28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -896,9 +896,9 @@ dependencies = [ [[package]] name = "data-url" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" [[package]] name = "derivative" @@ -4203,6 +4203,7 @@ dependencies = [ "aes-gcm", "brotli", "ctor", + "data-url", "dunce", "getrandom 0.2.12", "glob", diff --git a/core/tauri-runtime-wry/Cargo.toml b/core/tauri-runtime-wry/Cargo.toml index 136e6df35e0..e36d1d4bbc4 100644 --- a/core/tauri-runtime-wry/Cargo.toml +++ b/core/tauri-runtime-wry/Cargo.toml @@ -21,6 +21,7 @@ rand = "0.8" raw-window-handle = "0.5" tracing = { version = "0.1", optional = true } arboard = { version = "3", optional = true } +percent-encoding = "2.1" [target."cfg(windows)".dependencies] webview2-com = "0.19.1" @@ -32,7 +33,6 @@ webview2-com = "0.19.1" [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] gtk = { version = "0.15", features = [ "v3_20" ] } webkit2gtk = { version = "0.18.2", features = [ "v2_22" ] } -percent-encoding = "2.1" [target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies] cocoa = "0.24" diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index e4223e77700..8cd4735769d 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -3247,13 +3247,27 @@ fn create_webview( if window_builder.center { let _ = center_window(&window, window.inner_size()); } - let mut webview_builder = WebViewBuilder::new(window) - .map_err(|e| Error::CreateWebview(Box::new(e)))? + + let mut webview_builder = + WebViewBuilder::new(window).map_err(|e| Error::CreateWebview(Box::new(e)))?; + + // use with_html method if html content can be extracted from url. + // else defaults to with_url method + webview_builder = if let Some(html_string) = tauri_utils::html::extract_html_content(&url) { + webview_builder + .with_html(html_string) + .map_err(|e| Error::CreateWebview(Box::new(e)))? + } else { + webview_builder + .with_url(&url) + .map_err(|e| Error::CreateWebview(Box::new(e)))? + }; + + webview_builder = webview_builder .with_focused(focused) - .with_url(&url) - .unwrap() // safe to unwrap because we validate the URL beforehand .with_transparent(is_window_transparent) .with_accept_first_mouse(webview_attributes.accept_first_mouse); + if webview_attributes.file_drop_handler_enabled { webview_builder = webview_builder .with_file_drop_handler(create_file_drop_handler(window_event_listeners.clone())); diff --git a/core/tauri-runtime/Cargo.toml b/core/tauri-runtime/Cargo.toml index cccd75221da..f6b9bcfe0c6 100644 --- a/core/tauri-runtime/Cargo.toml +++ b/core/tauri-runtime/Cargo.toml @@ -50,3 +50,4 @@ system-tray = [ ] macos-private-api = [ ] global-shortcut = [ ] clipboard = [ ] +window-data-url = [ "tauri-utils/window-data-url" ] diff --git a/core/tauri-utils/Cargo.toml b/core/tauri-utils/Cargo.toml index a7c2748fa3e..32532adcb64 100644 --- a/core/tauri-utils/Cargo.toml +++ b/core/tauri-utils/Cargo.toml @@ -38,6 +38,7 @@ semver = "1" infer = "0.13" dunce = "1" log = "0.4.20" +data-url = { version = "0.3.1", optional = true } [target."cfg(target_os = \"linux\")".dependencies] heck = "0.4" @@ -54,3 +55,4 @@ process-relaunch-dangerous-allow-symlink-macos = [ ] config-json5 = [ "json5" ] config-toml = [ "toml" ] resources = [ "glob", "walkdir" ] +window-data-url = [ "data-url" ] diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index c9485884df5..29908fee0c8 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -54,12 +54,18 @@ pub enum WindowUrl { /// For instance, to load `tauri://localhost/users/john`, /// you can simply provide `users/john` in this configuration. App(PathBuf), + #[cfg(feature = "window-data-url")] + /// A data url, for example data:text/html,

Hello world

+ /// Data url should not be encoded + DataUrl(Url), } impl fmt::Display for WindowUrl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::External(url) => write!(f, "{url}"), + #[cfg(feature = "window-data-url")] + Self::DataUrl(url) => write!(f, "{url}"), Self::App(path) => write!(f, "{}", path.display()), } } @@ -3281,6 +3287,11 @@ mod build { let url = url_lit(url); quote! { #prefix::External(#url) } } + #[cfg(feature = "window-data-url")] + Self::DataUrl(url) => { + let url = url_lit(url); + quote! { #prefix::DataUrl(#url) } + } }) } } diff --git a/core/tauri-utils/src/html.rs b/core/tauri-utils/src/html.rs index 5b8af5772ca..6fd35698332 100644 --- a/core/tauri-utils/src/html.rs +++ b/core/tauri-utils/src/html.rs @@ -286,6 +286,20 @@ pub fn inline_isolation(document: &mut NodeRef, dir: &Path) { } } +/// Temporary naive method to check if a string is a html +pub fn is_html(data_string: &str) -> bool { + data_string.contains('<') && data_string.contains('>') +} + +/// Temporary naive method to extract data from html data string +pub fn extract_html_content(input: &str) -> Option<&str> { + if input.starts_with("data:text/html,") { + Some(&input[15..]) + } else { + None + } +} + #[cfg(test)] mod tests { use kuchiki::traits::*; diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 830530e086e..35d93d533e3 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -87,7 +87,7 @@ time = { version = "0.3", features = [ "parsing", "formatting" ], optional = tru os_info = { version = "3", optional = true } regex = { version = "1", optional = true } glob = "0.3" -data-url = { version = "0.2", optional = true } +data-url = { version = "0.3.1", optional = true } serialize-to-javascript = "=0.1.1" infer = { version = "0.9", optional = true } png = { version = "0.17", optional = true } @@ -184,7 +184,7 @@ macos-private-api = [ "tauri-runtime-wry/macos-private-api" ] windows7-compat = [ "win7-notifications" ] -window-data-url = [ "data-url" ] +window-data-url = [ "tauri-utils/window-data-url", "data-url" ] api-all = [ "clipboard-all", "dialog-all", diff --git a/core/tauri/src/manager.rs b/core/tauri/src/manager.rs index fd19adcd17a..ecb21296d31 100644 --- a/core/tauri/src/manager.rs +++ b/core/tauri/src/manager.rs @@ -995,6 +995,8 @@ impl WindowManager { } } WindowUrl::External(url) => url.clone(), + #[cfg(feature = "window-data-url")] + WindowUrl::DataUrl(url) => url.clone(), _ => unimplemented!(), }; @@ -1005,23 +1007,46 @@ impl WindowManager { )); } - #[cfg(feature = "window-data-url")] - if let Some(csp) = self.csp() { - if url.scheme() == "data" { - if let Ok(data_url) = data_url::DataUrl::process(url.as_str()) { - let (body, _) = data_url.decode_to_vec().unwrap(); - let html = String::from_utf8_lossy(&body).into_owned(); - // naive way to check if it's an html - if html.contains('<') && html.contains('>') { + match ( + url.scheme(), + tauri_utils::html::extract_html_content(url.as_str()), + ) { + #[cfg(feature = "window-data-url")] + ("data", Some(html_string)) => { + // There is an issue with the external DataUrl where HTML containing special characters + // are not correctly processed. A workaround is to first percent encode the html string, + // before it processed by DataUrl. + let encoded_string = percent_encoding::utf8_percent_encode(html_string, percent_encoding::NON_ALPHANUMERIC).to_string(); + let url = data_url::DataUrl::process(&format!("data:text/html,{}", encoded_string)) + .map_err(|_| crate::Error::InvalidWindowUrl("Failed to process data url")) + .and_then(|data_url| { + data_url + .decode_to_vec() + .map_err(|_| crate::Error::InvalidWindowUrl("Failed to decode processed data url")) + }) + .and_then(|(body, _)| { + let html = String::from_utf8_lossy(&body).into_owned(); let mut document = tauri_utils::html::parse(html); - tauri_utils::html::inject_csp(&mut document, &csp.to_string()); - url.set_path(&format!("text/html,{}", document.to_string())); - } - } + if let Some(csp) = self.csp() { + tauri_utils::html::inject_csp(&mut document, &csp.to_string()); + } + // decode back to raw html, as the content should be fully decoded + // when passing to wry / tauri-runtime-wry, which will be responsible + // for handling the encoding based on the OS. + let encoded_html = document.to_string(); + Ok( + percent_encoding::percent_decode_str(encoded_html.as_str()) + .decode_utf8_lossy() + .to_string(), + ) + }) + .unwrap_or(html_string.to_string()); + pending.url = format!("data:text/html,{}", url); } - } - - pending.url = url.to_string(); + _ => { + pending.url = url.to_string(); + } + }; if !pending.window_builder.has_icon() { if let Some(default_window_icon) = self.inner.default_window_icon.clone() {