()
+
+ private static func install(
+ on targetClass: AnyClass,
+ selector: Selector,
+ originalSelector: Selector,
+ replacementSelector: Selector
+ ) -> Bool {
+ guard
+ let method = class_getInstanceMethod(targetClass, selector),
+ let replacement = class_getInstanceMethod(FileDropBridge.self, replacementSelector)
+ else {
+ return false
+ }
+
+ let originalImplementation = method_getImplementation(method)
+ let typeEncoding = method_getTypeEncoding(method)
+ class_addMethod(targetClass, selector, originalImplementation, typeEncoding)
+
+ guard let targetMethod = class_getInstanceMethod(targetClass, selector) else {
+ return false
+ }
+
+ class_addMethod(targetClass, originalSelector, originalImplementation, typeEncoding)
+ method_setImplementation(targetMethod, method_getImplementation(replacement))
+ return true
+ }
+}
+
+private extension NSView {
+ var enclosingFileDropWebView: FileDropWebView? {
+ var view: NSView? = self
+
+ while let current = view {
+ if let webView = current as? FileDropWebView {
+ return webView
+ }
+
+ view = current.superview
+ }
+
+ return nil
+ }
+
+ @objc(edw_originalDraggingEntered:)
+ dynamic func edw_originalDraggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
+ []
+ }
+
+ @objc(edw_originalDraggingUpdated:)
+ dynamic func edw_originalDraggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
+ []
+ }
+
+ @objc(edw_originalPrepareForDragOperation:)
+ dynamic func edw_originalPrepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
+ false
+ }
+
+ @objc(edw_originalPerformDragOperation:)
+ dynamic func edw_originalPerformDragOperation(_ sender: NSDraggingInfo) -> Bool {
+ false
+ }
+
+ @objc(edw_originalDraggingExited:)
+ dynamic func edw_originalDraggingExited(_ sender: NSDraggingInfo?) {}
+}
+
final class FileDropWebView: WKWebView {
private static let fileDragTypes =
[
@@ -14,10 +181,74 @@ final class FileDropWebView: WKWebView {
override init(frame: NSRect, configuration: WKWebViewConfiguration) {
super.init(frame: frame, configuration: configuration)
registerForDraggedTypes(Self.fileDragTypes)
+ installDragBridge()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
registerForDraggedTypes(Self.fileDragTypes)
+ installDragBridge()
+ }
+
+ override func viewDidMoveToWindow() {
+ super.viewDidMoveToWindow()
+ installDragBridge()
+ DispatchQueue.main.async { [weak self] in
+ self?.installDragBridge()
+ }
+ }
+
+ fileprivate func acceptsFileDrop(_ sender: NSDraggingInfo) -> Bool {
+ let types = sender.draggingPasteboard.types ?? []
+ return types.contains { Self.fileDragTypes.contains($0) }
+ }
+
+ fileprivate func fileDropOperation(
+ _ operation: NSDragOperation,
+ accepted: Bool
+ ) -> NSDragOperation {
+ accepted && operation.isEmpty ? .copy : operation
+ }
+
+ override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
+ let acceptedFileDrop = acceptsFileDrop(sender)
+ return fileDropOperation(super.draggingEntered(sender), accepted: acceptedFileDrop)
+ }
+
+ override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
+ let acceptedFileDrop = acceptsFileDrop(sender)
+ return fileDropOperation(super.draggingUpdated(sender), accepted: acceptedFileDrop)
+ }
+
+ override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
+ super.prepareForDragOperation(sender) || acceptsFileDrop(sender)
+ }
+
+ override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
+ super.performDragOperation(sender)
+ }
+
+ override func draggingExited(_ sender: NSDraggingInfo?) {
+ super.draggingExited(sender)
+ }
+
+ private func installDragBridge() {
+ for view in privateDragViews(in: self) {
+ view.registerForDraggedTypes(Self.fileDragTypes)
+ FileDropBridge.install(on: view)
+ }
+ }
+
+ private func privateDragViews(in view: NSView) -> [NSView] {
+ view.subviews.flatMap { subview in
+ let nested = privateDragViews(in: subview)
+ let name = NSStringFromClass(type(of: subview))
+
+ if name.contains("WKContentView") || name.contains("WKView") {
+ return [subview] + nested
+ }
+
+ return nested
+ }
}
}
diff --git a/test/e2e/e2e_test.exs b/test/e2e/e2e_test.exs
index d1427c9..ec4c618 100644
--- a/test/e2e/e2e_test.exs
+++ b/test/e2e/e2e_test.exs
@@ -135,7 +135,8 @@ defmodule DesktopWebview.E2ETest do
multiple: document.querySelector("#directory-files").multiple,
webkitdirectory: document.querySelector("#directory-files").hasAttribute("webkitdirectory"),
directory: document.querySelector("#directory-files").hasAttribute("directory")
- }
+ },
+ drop_target: document.querySelector("#drop-target").id
})
"""
},
@@ -151,7 +152,8 @@ defmodule DesktopWebview.E2ETest do
"multiple" => true,
"webkitdirectory" => true,
"directory" => true
- }
+ },
+ "drop_target" => "drop-target"
}} = Jason.decode(result)
assert {:ok, true} = Transport.call("window.destroy", %{"window_id" => wid})
diff --git a/test/fixtures/file_input.html b/test/fixtures/file_input.html
index 12aea94..c28fab1 100644
--- a/test/fixtures/file_input.html
+++ b/test/fixtures/file_input.html
@@ -25,9 +25,11 @@ HTML file input fixture
+ Drop files here to test file-manager drag-and-drop.