diff --git a/.changes/macos-drag-drop-modern-pasteboard.md b/.changes/macos-drag-drop-modern-pasteboard.md new file mode 100644 index 000000000..12e19ace3 --- /dev/null +++ b/.changes/macos-drag-drop-modern-pasteboard.md @@ -0,0 +1,5 @@ +--- +"wry": patch +--- + +On macOS, use the modern `readObjectsForClasses:options:` pasteboard API to collect dragged file paths. This fixes a panic when the drag source publishes only the modern per-item `public.file-url` type (as `NSDraggingItem.initWithPasteboardWriter:` does) instead of the legacy `NSFilenamesPboardType`, and removes the deprecated API as the primary path. The legacy type is kept as a defensive fallback. diff --git a/src/wkwebview/drag_drop.rs b/src/wkwebview/drag_drop.rs index e28364e4a..ca10c8b0f 100644 --- a/src/wkwebview/drag_drop.rs +++ b/src/wkwebview/drag_drop.rs @@ -6,27 +6,58 @@ use std::{ffi::CStr, path::PathBuf}; use objc2::{ runtime::{Bool, ProtocolObject}, - DeclaredClass, + ClassType, DeclaredClass, }; use objc2_app_kit::{NSDragOperation, NSDraggingInfo, NSFilenamesPboardType}; -use objc2_foundation::{NSArray, NSPoint, NSRect, NSString}; +use objc2_foundation::{NSArray, NSPoint, NSRect, NSString, NSURL}; use crate::DragDropEvent; use super::WryWebView; pub(crate) unsafe fn collect_paths(drag_info: &ProtocolObject) -> Vec { + // Prefer the modern `readObjectsForClasses:options:` API with `NSURL`. It works + // for sources that publish per-item `public.file-url` types (the standard for + // `NSDraggingItem.initWithPasteboardWriter:` with `NSPasteboardItem`) as well + // as the legacy `NSFilenamesPboardType` payload, so it covers both paths in + // one call. + // + // The legacy `NSFilenamesPboardType` branch below is kept as a defensive + // fallback in case `readObjectsForClasses:options:` returns nothing on some + // configuration; it should not normally be reached. + // + // No `unwrap` calls: every conversion that can fail skips the entry, so a + // malformed pasteboard never panics the webview process. let pb = drag_info.draggingPasteboard(); let mut drag_drop_paths = Vec::new(); - let types = NSArray::arrayWithObject(NSFilenamesPboardType); + let url_classes = NSArray::from_slice(&[NSURL::class()]); + if let Some(items) = pb.readObjectsForClasses_options(&url_classes, None) { + for item in &items { + if let Some(url) = item.downcast_ref::() { + if let Some(path) = url.path() { + let path = CStr::from_ptr(path.UTF8String()).to_string_lossy(); + drag_drop_paths.push(PathBuf::from(path.into_owned())); + } + } + } + if !drag_drop_paths.is_empty() { + return drag_drop_paths; + } + } + + // Legacy fallback: read `NSFilenamesPboardType` directly. + let types = NSArray::arrayWithObject(NSFilenamesPboardType); if pb.availableTypeFromArray(&types).is_some() { - let paths = pb.propertyListForType(NSFilenamesPboardType).unwrap(); - let paths = paths.downcast::().unwrap(); - for path in paths { - let path = path.downcast::().unwrap(); - let path = CStr::from_ptr(path.UTF8String()).to_string_lossy(); - drag_drop_paths.push(PathBuf::from(path.into_owned())); + if let Some(paths) = pb.propertyListForType(NSFilenamesPboardType) { + if let Ok(paths) = paths.downcast::() { + for path in paths { + if let Ok(path) = path.downcast::() { + let path = CStr::from_ptr(path.UTF8String()).to_string_lossy(); + drag_drop_paths.push(PathBuf::from(path.into_owned())); + } + } + } } } drag_drop_paths