From b1d9ffa1abc9eff65acf16792b4fb33d9c45ba8a Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Tue, 13 Aug 2024 07:38:49 -0300 Subject: [PATCH] fix(core): IPC fallback hanging when sending responses, closes #10327 (#10582) The IPC fallback system kicks in when the custom protocol implementation cannot be used (e.g. CORS issues). The fallback uses the postMessage mechanism, which by default uses channels to send large responses. If the custom protocol implementation cannot be used, we should not use channels, but eval the response directly. --- .changes/fix-ipc-fallback.md | 5 +++++ core/tauri/scripts/ipc-protocol.js | 11 +++++++++-- core/tauri/src/ipc/protocol.rs | 25 +++++++++++++++++++------ 3 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 .changes/fix-ipc-fallback.md diff --git a/.changes/fix-ipc-fallback.md b/.changes/fix-ipc-fallback.md new file mode 100644 index 000000000..6541e2792 --- /dev/null +++ b/.changes/fix-ipc-fallback.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:bug +--- + +Fix IPC fallback (postMessage implementation when custom protocol fails) hanging when sending responses. diff --git a/core/tauri/scripts/ipc-protocol.js b/core/tauri/scripts/ipc-protocol.js index 3dad56907..f55ca1586 100644 --- a/core/tauri/scripts/ipc-protocol.js +++ b/core/tauri/scripts/ipc-protocol.js @@ -62,7 +62,11 @@ ) } }) - .catch(() => { + .catch((e) => { + console.warn( + 'IPC custom protocol failed, Tauri will now use the postMessage interface instead', + e + ) // failed to use the custom protocol IPC (either the webview blocked a custom protocol or it was a CSP error) // so we need to fallback to the postMessage interface customProtocolIpcFailed = true @@ -74,7 +78,10 @@ cmd, callback, error, - options, + options: { + ...options, + customProtocolIpcBlocked: customProtocolIpcFailed + }, payload, __TAURI_INVOKE_KEY__ }) diff --git a/core/tauri/src/ipc/protocol.rs b/core/tauri/src/ipc/protocol.rs index ad67d70b2..d419048e6 100644 --- a/core/tauri/src/ipc/protocol.rs +++ b/core/tauri/src/ipc/protocol.rs @@ -174,6 +174,7 @@ fn handle_ipc_message(request: Request, manager: &AppManager use serde::{Deserialize, Deserializer}; + #[derive(Default)] pub(crate) struct HeaderMap(http::HeaderMap); impl<'de> Deserialize<'de> for HeaderMap { @@ -199,9 +200,13 @@ fn handle_ipc_message(request: Request, manager: &AppManager } } - #[derive(Deserialize)] + #[derive(Deserialize, Default)] + #[serde(rename_all = "camelCase")] struct RequestOptions { + #[serde(default)] headers: HeaderMap, + #[serde(default)] + custom_protocol_ipc_blocked: bool, } #[derive(Deserialize)] @@ -260,13 +265,15 @@ fn handle_ipc_message(request: Request, manager: &AppManager match message { Ok(message) => { + let options = message.options.unwrap_or_default(); + let request = InvokeRequest { cmd: message.cmd, callback: message.callback, error: message.error, url: Url::parse(&request.uri().to_string()).expect("invalid IPC request URL"), body: message.payload.into(), - headers: message.options.map(|o| o.headers.0).unwrap_or_default(), + headers: options.headers.0, invoke_key: message.invoke_key, }; @@ -293,9 +300,7 @@ fn handle_ipc_message(request: Request, manager: &AppManager .entered(); // the channel data command is the only command that uses a custom protocol on Linux - if webview.manager().webview.invoke_responder.is_none() - && cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND - { + if webview.manager().webview.invoke_responder.is_none() { fn responder_eval( webview: &crate::Webview, js: crate::Result, @@ -310,6 +315,10 @@ fn handle_ipc_message(request: Request, manager: &AppManager let _ = webview.eval(&eval_js); } + let can_use_channel_for_response = cmd + != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND + && !options.custom_protocol_ipc_blocked; + #[cfg(feature = "tracing")] let _response_span = tracing::trace_span!( "ipc::request::response", @@ -327,6 +336,7 @@ fn handle_ipc_message(request: Request, manager: &AppManager InvokeResponse::Ok(InvokeBody::Json(v)) => { if !(cfg!(target_os = "macos") || cfg!(target_os = "ios")) && matches!(v, JsonValue::Object(_) | JsonValue::Array(_)) + && can_use_channel_for_response { let _ = Channel::from_callback_fn(webview, callback).send(v); } else { @@ -338,7 +348,10 @@ fn handle_ipc_message(request: Request, manager: &AppManager } } InvokeResponse::Ok(InvokeBody::Raw(v)) => { - if cfg!(target_os = "macos") || cfg!(target_os = "ios") { + if cfg!(target_os = "macos") + || cfg!(target_os = "ios") + || !can_use_channel_for_response + { responder_eval( &webview, format_callback_result(Result::<_, ()>::Ok(v), callback, error),