Skip to content

Commit

Permalink
fix(macos): Prevent NSExceptions when dropping a webview with registe…
Browse files Browse the repository at this point in the history
…red protocols (#1215)

* fix(macos): Prevent NSExceptions when dropping a webview with registered protocols

fixes tauri-apps/tauri#7898

* use counter and hashset

* retain -> remove

* Update mod.rs

* that's what you get from not walking 3 steps to get your macbook
  • Loading branch information
FabianLars authored Apr 15, 2024
1 parent 44cb683 commit 3e3d59c
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changes/macos-drop-panic.md
Original file line number Diff line number Diff line change
@@ -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.
32 changes: 32 additions & 0 deletions src/wkwebview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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},
Expand Down Expand Up @@ -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<Mutex<HashSet<u32>>> = Lazy::new(Default::default);

pub(crate) struct InnerWebView {
pub webview: id,
pub manager: id,
Expand All @@ -86,6 +94,7 @@ pub(crate) struct InnerWebView {
drag_drop_ptr: *mut Box<dyn Fn(crate::DragDropEvent) -> bool>,
download_delegate: id,
protocol_ptrs: Vec<*mut Box<dyn Fn(Request<Vec<u8>>, RequestAsyncResponder)>>,
webview_id: u32,
}

impl InnerWebView {
Expand Down Expand Up @@ -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::<u32>("webview_id");

let function = this.get_ivar::<*mut c_void>("function");
if !function.is_null() {
let function =
Expand Down Expand Up @@ -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];
},
);
Expand All @@ -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
Expand All @@ -318,6 +345,7 @@ impl InnerWebView {
let cls = match cls {
Some(mut cls) => {
cls.add_ivar::<*mut c_void>("function");
cls.add_ivar::<u32>("webview_id");
cls.add_method(
sel!(webView:startURLSchemeTask:),
start_task as extern "C" fn(&Object, Sel, id, id),
Expand All @@ -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)];
}

Expand Down Expand Up @@ -878,6 +907,7 @@ impl InnerWebView {
download_delegate,
protocol_ptrs,
is_child,
webview_id,
};

// Initialize scripts
Expand Down Expand Up @@ -1245,6 +1275,8 @@ pub fn platform_webview_version() -> Result<String> {

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() {
Expand Down
18 changes: 18 additions & 0 deletions src/wkwebview/util.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit 3e3d59c

Please sign in to comment.