diff --git a/.changes/macos-drop-panic.md b/.changes/macos-drop-panic.md new file mode 100644 index 000000000..c026787f7 --- /dev/null +++ b/.changes/macos-drop-panic.md @@ -0,0 +1,5 @@ +--- +"wry": patch +--- + +On macOS, prevent NSExceptions and invalid memory access panics when dropping the WebView while custom protocols handlers may still be running. diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index b32833635..658bbad2c 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -10,6 +10,7 @@ mod navigation; mod proxy; #[cfg(target_os = "macos")] mod synthetic_mouse_events; +mod util; #[cfg(target_os = "macos")] use cocoa::appkit::{NSView, NSViewHeightSizable, NSViewMinYMargin, NSViewWidthSizable}; @@ -18,10 +19,12 @@ use cocoa::{ foundation::{NSDictionary, NSFastEnumeration, NSInteger}, }; use dpi::{LogicalPosition, LogicalSize}; +use once_cell::sync::Lazy; use raw_window_handle::{HasWindowHandle, RawWindowHandle}; use std::{ borrow::Cow, + collections::HashSet, ffi::{c_void, CStr}, os::raw::c_char, ptr::{null, null_mut}, @@ -65,12 +68,17 @@ use http::{ Request, Response as HttpResponse, }; +use self::util::Counter; + const IPC_MESSAGE_HANDLER_NAME: &str = "ipc"; #[cfg(target_os = "macos")] const ACCEPT_FIRST_MOUSE: &str = "accept_first_mouse"; const NS_JSON_WRITING_FRAGMENTS_ALLOWED: u64 = 4; +static COUNTER: Counter = Counter::new(); +static WEBVIEW_IDS: Lazy>> = Lazy::new(Default::default); + pub(crate) struct InnerWebView { pub webview: id, pub manager: id, @@ -86,6 +94,7 @@ pub(crate) struct InnerWebView { drag_drop_ptr: *mut Box bool>, download_delegate: id, protocol_ptrs: Vec<*mut Box>, RequestAsyncResponder)>>, + webview_id: u32, } impl InnerWebView { @@ -172,6 +181,8 @@ impl InnerWebView { #[cfg(feature = "tracing")] let span = tracing::info_span!("wry::custom_protocol::handle", uri = tracing::field::Empty) .entered(); + let webview_id = *this.get_ivar::("webview_id"); + let function = this.get_ivar::<*mut c_void>("function"); if !function.is_null() { let function = @@ -271,14 +282,25 @@ impl InnerWebView { let urlresponse: id = msg_send![class!(NSHTTPURLResponse), alloc]; let response: id = msg_send![urlresponse, initWithURL:url statusCode: wanted_status_code HTTPVersion:NSString::new(&wanted_version) headerFields:headers]; + if !WEBVIEW_IDS.lock().unwrap().contains(&webview_id) { + return; + } let () = msg_send![task, didReceiveResponse: response]; // Send data let bytes = content.as_ptr() as *mut c_void; let data: id = msg_send![class!(NSData), alloc]; let data: id = msg_send![data, initWithBytesNoCopy:bytes length:content.len() freeWhenDone: if content.len() == 0 { NO } else { YES }]; + + if !WEBVIEW_IDS.lock().unwrap().contains(&webview_id) { + return; + } let () = msg_send![task, didReceiveData: data]; + // Finish + if !WEBVIEW_IDS.lock().unwrap().contains(&webview_id) { + return; + } let () = msg_send![task, didFinish]; }, ); @@ -299,6 +321,11 @@ impl InnerWebView { } extern "C" fn stop_task(_: &Object, _: Sel, _webview: id, _task: id) {} + let mut wv_ids = WEBVIEW_IDS.lock().unwrap(); + let webview_id = COUNTER.next(); + wv_ids.insert(webview_id); + drop(wv_ids); + // Safety: objc runtime calls are unsafe unsafe { // Config and custom protocol @@ -318,6 +345,7 @@ impl InnerWebView { let cls = match cls { Some(mut cls) => { cls.add_ivar::<*mut c_void>("function"); + cls.add_ivar::("webview_id"); cls.add_method( sel!(webView:startURLSchemeTask:), start_task as extern "C" fn(&Object, Sel, id, id), @@ -335,6 +363,7 @@ impl InnerWebView { protocol_ptrs.push(function); (*handler).set_ivar("function", function as *mut _ as *mut c_void); + (*handler).set_ivar("webview_id", webview_id); let () = msg_send![config, setURLSchemeHandler:handler forURLScheme:NSString::new(&name)]; } @@ -878,6 +907,7 @@ impl InnerWebView { download_delegate, protocol_ptrs, is_child, + webview_id, }; // Initialize scripts @@ -1245,6 +1275,8 @@ pub fn platform_webview_version() -> Result { impl Drop for InnerWebView { fn drop(&mut self) { + WEBVIEW_IDS.lock().unwrap().remove(&self.webview_id); + // We need to drop handler closures here unsafe { if !self.ipc_handler_ptr.is_null() { diff --git a/src/wkwebview/util.rs b/src/wkwebview/util.rs new file mode 100644 index 000000000..bca97787b --- /dev/null +++ b/src/wkwebview/util.rs @@ -0,0 +1,18 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::sync::atomic::{AtomicU32, Ordering}; + +pub struct Counter(AtomicU32); + +impl Counter { + #[allow(unused)] + pub const fn new() -> Self { + Self(AtomicU32::new(1)) + } + + pub fn next(&self) -> u32 { + self.0.fetch_add(1, Ordering::Relaxed) + } +}