Skip to content
Open
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/macos-drag-drop-modern-pasteboard.md
Original file line number Diff line number Diff line change
@@ -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.
49 changes: 40 additions & 9 deletions src/wkwebview/drag_drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn NSDraggingInfo>) -> Vec<PathBuf> {
// 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::<NSURL>() {
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::<NSArray>().unwrap();
for path in paths {
let path = path.downcast::<NSString>().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::<NSArray>() {
for path in paths {
if let Ok(path) = path.downcast::<NSString>() {
let path = CStr::from_ptr(path.UTF8String()).to_string_lossy();
drag_drop_paths.push(PathBuf::from(path.into_owned()));
}
}
}
}
}
drag_drop_paths
Expand Down
Loading