diff --git a/.changes/change-pr-13288.md b/.changes/change-pr-13288.md new file mode 100644 index 000000000000..b7589dc1d8d8 --- /dev/null +++ b/.changes/change-pr-13288.md @@ -0,0 +1,5 @@ +--- +"tauri": 'patch:bug' +--- + +Prevent the JavaScript runtime crashing when channel events fire in a webview that no longer has callbacks for the channel. diff --git a/crates/tauri/src/ipc/channel.rs b/crates/tauri/src/ipc/channel.rs index c51089ddae30..87ee4b7f8327 100644 --- a/crates/tauri/src/ipc/channel.rs +++ b/crates/tauri/src/ipc/channel.rs @@ -20,7 +20,9 @@ use crate::{ Manager, Runtime, State, Webview, }; -use super::{CallbackFn, InvokeError, InvokeResponseBody, IpcResponse, Request, Response}; +use super::{ + format_callback, CallbackFn, InvokeError, InvokeResponseBody, IpcResponse, Request, Response, +}; pub const IPC_PAYLOAD_PREFIX: &str = "__CHANNEL__:"; // TODO: Change this to `channel` in v3 @@ -149,15 +151,14 @@ impl JavaScriptChannelId { match body { // Don't go through the fetch process if the payload is small InvokeResponseBody::Json(string) if string.len() < MAX_JSON_DIRECT_EXECUTE_THRESHOLD => { - webview.eval(format!( - "window['_{callback_id}']({{ message: {string}, index: {current_index} }})" + webview.eval(format_callback::format_raw_js( + callback_id, + &format!("{{ message: {string}, index: {current_index} }}"), ))?; } InvokeResponseBody::Raw(bytes) if bytes.len() < MAX_RAW_DIRECT_EXECUTE_THRESHOLD => { let bytes_as_json_array = serde_json::to_string(&bytes)?; - webview.eval(format!( - "window['_{callback_id}']({{ message: new Uint8Array({bytes_as_json_array}).buffer, index: {current_index} }})", - ))?; + webview.eval(format_callback::format_raw_js(callback_id, &format!("{{ message: new Uint8Array({bytes_as_json_array}).buffer, index: {current_index} }}")))?; } // use the fetch API to speed up larger response payloads _ => { @@ -180,8 +181,9 @@ impl JavaScriptChannelId { }), Some(Box::new(move || { let current_index = counter_clone.load(Ordering::Relaxed); - let _ = webview_clone.eval(format!( - "window['_{callback_id}']({{ end: true, index: {current_index} }})", + let _ = webview_clone.eval(format_callback::format_raw_js( + callback_id, + &format!("{{ end: true, index: {current_index} }}"), )); })), ) @@ -243,12 +245,13 @@ impl Channel { match body { // Don't go through the fetch process if the payload is small InvokeResponseBody::Json(string) if string.len() < MAX_JSON_DIRECT_EXECUTE_THRESHOLD => { - webview.eval(format!("window['_{callback_id}']({string})"))?; + webview.eval(format_callback::format_raw_js(callback_id, &string))?; } InvokeResponseBody::Raw(bytes) if bytes.len() < MAX_RAW_DIRECT_EXECUTE_THRESHOLD => { let bytes_as_json_array = serde_json::to_string(&bytes)?; - webview.eval(format!( - "window['_{callback_id}'](new Uint8Array({bytes_as_json_array}).buffer)", + webview.eval(format_callback::format_raw_js( + callback_id, + &format!("new Uint8Array({bytes_as_json_array}).buffer"), ))?; } // use the fetch API to speed up larger response payloads diff --git a/crates/tauri/src/ipc/format_callback.rs b/crates/tauri/src/ipc/format_callback.rs index decdd854b86e..8ae2ebf0b7d4 100644 --- a/crates/tauri/src/ipc/format_callback.rs +++ b/crates/tauri/src/ipc/format_callback.rs @@ -92,18 +92,22 @@ pub fn format(function_name: CallbackFn, arg: &T) -> crate::Result /// See [json-parse-benchmark](https://github.com/GoogleChromeLabs/json-parse-benchmark). pub fn format_raw(function_name: CallbackFn, json_string: String) -> crate::Result { serialize_js_with(json_string, Default::default(), |arg| { - format!( - r#" - if (window["_{fn}"]) {{ - window["_{fn}"]({arg}) - }} else {{ - console.warn("[TAURI] Couldn't find callback id {fn} in window. This happens when the app is reloaded while Rust is running an asynchronous operation.") - }}"#, - fn = function_name.0 - ) + format_raw_js(function_name.0, arg) }) } +/// Formats a callback function invocation, properly accounting for error handling. +pub fn format_raw_js(id: u32, js: &str) -> String { + format!( + r#" + if (window["_{id}"]) {{ + window["_{id}"]({js}) + }} else {{ + console.warn("[TAURI] Couldn't find callback id {id} in window. This happens when the app is reloaded while Rust is running an asynchronous operation.") + }}"# + ) +} + /// Formats a serializable Result type to its Promise response. /// /// See [`format_result_raw`] for more information.