Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/change-pr-13288.md
Original file line number Diff line number Diff line change
@@ -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.
25 changes: 14 additions & 11 deletions crates/tauri/src/ipc/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
_ => {
Expand All @@ -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} }}"),
));
})),
)
Expand Down Expand Up @@ -243,12 +245,13 @@ impl<TSend> Channel<TSend> {
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
Expand Down
22 changes: 13 additions & 9 deletions crates/tauri/src/ipc/format_callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,22 @@ pub fn format<T: Serialize>(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<String> {
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.
Expand Down