diff --git a/README.md b/README.md index e12d6bc..18b881c 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,12 @@ spawns the BEAM release; in development an existing BEAM can launch the host wit | Windows | WebView2 | Done — [docs/status/windows.md](docs/status/windows.md) | | Linux | WebKitGTK | Usable — [docs/status/linux.md](docs/status/linux.md) | -HTML `` support is required on all three hosts. The shared E2E -checks the fixture's DOM contract; native picker selection remains a manual check. -It is separate from the `dialog.choose_file` RPC. See [docs/protocol.md](docs/protocol.md). +HTML `` support and file-manager drag-and-drop are required on +all three hosts. Finder, Explorer, and Linux file-manager drops must reach the +page as normal web drag events with a usable `DataTransfer.files` list. The +shared E2E checks the fixture's DOM contract; native picker and file-drop checks +remain manual. These features are separate from the `dialog.choose_file` RPC. +See [docs/protocol.md](docs/protocol.md). ## Quick start (development) diff --git a/docs/porting.md b/docs/porting.md index 250163b..6565817 100644 --- a/docs/porting.md +++ b/docs/porting.md @@ -44,21 +44,23 @@ Do **not** copy macOS UI code into other platforms — share only the protocol. 10. **Packaged BEAM spawn** + **CI artifact** on tag draft releases 11. **Test RPC** behind `--edw-test-rpc`; run shared E2E -## HTML file chooser +## HTML file inputs and file-manager drag-and-drop -`` is required on every platform. It is separate from -`dialog.choose_file`, which is an explicit Elixir RPC. Follow the semantics in -[protocol.md](protocol.md) and use the platform hook below. +`` and file-manager drag-and-drop are required on every +platform. They are separate from `dialog.choose_file`, which is an explicit +Elixir RPC. Follow the semantics in [protocol.md](protocol.md) and use the +platform hooks below. -| Platform | Hook | Required integration | +| Platform | Hooks | Required integration | |----------|------|----------------------| -| macOS | `WKUIDelegate.webView(_:runOpenPanelWith:initiatedByFrame:completionHandler:)` | Map `WKOpenPanelParameters` to the native panel. Pass selected URLs to the completion handler, or `nil` on cancel. | -| Windows | WebView2's built-in file picker | Keep the WebView2 UI thread and message loop active. WebView2 has no native file-chooser event for this input; do not replace it with `dialog.choose_file` or CDP file injection. | -| Linux | WebKitGTK `run-file-chooser` default handler | Keep WebKitGTK's asynchronous default handler enabled, or provide an equivalent handler that completes the request with selected paths or cancellation. | +| macOS | `WKUIDelegate.webView(_:runOpenPanelWith:initiatedByFrame:completionHandler:)` and `NSDraggingDestination` | Map `WKOpenPanelParameters` to the native panel. Register file URLs and file promises on the webview. Forward accepted drag methods to WebKit so the page receives its `FileList`. | +| Windows | WebView2's built-in file picker and drag handling | Keep the WebView2 UI thread and message loop active. Do not replace the picker or file drops with `dialog.choose_file`, CDP injection, or a host-only drop handler. | +| Linux | WebKitGTK `run-file-chooser` default handler and drag handling | Keep the asynchronous chooser and normal WebKitGTK drag handling enabled. Provide equivalent handlers only when they preserve the page `drop` event and `dataTransfer.files`. | The shared E2E checks the fixture's DOM contract only. It cannot drive a native picker or inject a `FileList`; selection, cancellation, multiple files, and -directory selection need manual checks until a supported platform test hook exists. +directory selection, and file-manager drops need manual checks until a supported +platform test hook exists. ## Toolchain expectations diff --git a/docs/protocol.md b/docs/protocol.md index bd6589d..eae584f 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -154,6 +154,24 @@ The shared E2E may inspect the fixture's DOM, but it cannot select files or populate a `FileList` through JavaScript. Native picker selection and cancellation remain manual checks until a supported platform test hook exists. +### File-manager drag-and-drop + +File-manager drag-and-drop is also a required webview feature on macOS, Windows, +and Linux. A drop from Finder, Explorer, or a Linux file manager onto a page +must produce the normal web `drop` event. Its `dataTransfer.files` must contain +the dropped local files. + +Hosts MUST: + +- accept local file URLs and file promises from the platform file manager; +- preserve the browser's normal drop handling and `FileList` semantics; +- support multiple dropped files when the page accepts them; and +- avoid converting a page drop into `dialog.choose_file` or another RPC. + +The webview host may inspect the drag to decide whether to accept it, but it must +forward accepted drops to the engine. Applications still decide which files to +accept through their page and upload logic. + ### Menus and tray - The macOS host installs a default `Edit` submenu on the main menu (Undo, Redo, diff --git a/docs/status/linux.md b/docs/status/linux.md index 887323f..a94cf33 100644 --- a/docs/status/linux.md +++ b/docs/status/linux.md @@ -32,7 +32,7 @@ Host: GTK 4 + WebKitGTK 6 (`native/linux/`). Binary delivery via GitHub Releases | Permission policy hybrid | done | | | Microphone in webview | done | E2E via test RPC + fixture | | Camera in webview | done | E2E via test RPC + fixture | -| HTML `` | partial | WebKitGTK's asynchronous `run-file-chooser` default handler; native selection and cancellation checks pending | +| HTML `` and file-manager drag-and-drop | partial | WebKitGTK default chooser and drag handling; native picker and file-manager checks pending | | Test RPC channel | done | `--edw-test-rpc` | | Release artifact download | todo | | | CI build | done | ubuntu-latest + xvfb | diff --git a/docs/status/macos.md b/docs/status/macos.md index 864a5d1..a1c08a1 100644 --- a/docs/status/macos.md +++ b/docs/status/macos.md @@ -33,7 +33,7 @@ manual-only with justification). | Microphone in webview | done | E2E via test RPC + fixture | | Camera in webview | done | E2E via test RPC + fixture | | Native dialogs (`dialog.choose_file/dir`) | done | `NSOpenPanel` (manual; blocks RPC) | -| HTML `` | partial | `WKUIDelegate` open-panel hook; native selection and cancellation checks pending | +| HTML `` and Finder drag-and-drop | partial | `WKUIDelegate` open-panel hook and `FileDropWebView` forwarding; native picker and Finder checks pending | | Dialog prompt | done | `NSAlert` + text field (manual) | | EventBridge Env/Window/Menu | done | Elixir unit coverage | | Test RPC channel | done | `--edw-test-rpc` | diff --git a/docs/status/windows.md b/docs/status/windows.md index c8783bc..24121dc 100644 --- a/docs/status/windows.md +++ b/docs/status/windows.md @@ -33,7 +33,7 @@ Release asset: `DesktopWebView-windows-x64.exe` (GitHub Releases; not Hex `priv/ | Microphone in webview | done | Permission RPC + WebView2 kinds | | Camera in webview | done | Permission RPC + WebView2 kinds | | Native dialogs (`dialog.choose_file/dir`) | done | IFileOpenDialog + Win32 prompt | -| HTML `` | partial | WebView2's built-in picker; native selection and cancellation checks pending | +| HTML `` and Explorer drag-and-drop | partial | WebView2 built-in picker and drag handling; native picker and Explorer checks pending | | Host-driven BEAM restart | done | `restart_beam` ini + process wait | | Test RPC channel | done | E2E | | Release artifact download | todo | Elixir fetch/cache still pending | diff --git a/native/linux/README.md b/native/linux/README.md index 174a1b9..54a50b2 100644 --- a/native/linux/README.md +++ b/native/linux/README.md @@ -46,17 +46,21 @@ Release artifact name: `DesktopWebView-linux-x86_64` (see [packaging.md](../../d Headless CI needs a display (`xvfb-run` or a pre-set `DISPLAY`). -## HTML file inputs +## HTML file inputs and file-manager drops WebKitGTK's asynchronous `run-file-chooser` default handler serves ``. It handles single files, multiple files, directories, and cancellation without using the `dialog.choose_file` RPC. +File-manager drops are also required. Keep WebKitGTK's normal drag handling +enabled so file URLs reach the page as a `drop` event with +`dataTransfer.files`. Do not replace the page drop with `dialog.choose_file`. + The shared E2E checks the fixture DOM, but it cannot drive a native picker. Manually verify selection and cancellation with `test/fixtures/file_input.html`. The chooser uses the GTK desktop portal when -the desktop session provides one, so the host needs an interactive display for -manual checks. +the desktop session provides one. File-manager drop checks also need an +interactive display. ## Structure diff --git a/native/macos/README.md b/native/macos/README.md index 4cea905..3c37c70 100644 --- a/native/macos/README.md +++ b/native/macos/README.md @@ -21,15 +21,17 @@ Output is copied to `priv/native/macos/DesktopWebView` (universal when both arch # prints: listening ``` -## HTML file inputs +## HTML file inputs and Finder drops `` uses the `WKUIDelegate` open-panel callback in `WebWindow.swift`. The callback maps single, multiple, directory, and cancel -actions to WebKit's `FileList`. +actions to WebKit's `FileList`. `FileDropWebView.swift` registers file URLs and +file promises, accepts Finder drops, and forwards the drop methods to WebKit. -The shared E2E checks the fixture DOM, but it cannot drive the macOS picker. -Manually verify selection and cancellation with -`test/fixtures/file_input.html`, including after `webview.rebuild`. +The shared E2E checks the fixture DOM, but it cannot drive the macOS picker or +Finder. Manually verify selection, cancellation, multiple files, directory +selection, and Finder drops with `test/fixtures/file_input.html`, including +after `webview.rebuild`. ## Structure diff --git a/native/macos/Sources/DesktopWebView/FileDropWebView.swift b/native/macos/Sources/DesktopWebView/FileDropWebView.swift new file mode 100644 index 0000000..20b433b --- /dev/null +++ b/native/macos/Sources/DesktopWebView/FileDropWebView.swift @@ -0,0 +1,23 @@ +import AppKit +import WebKit + +final class FileDropWebView: WKWebView { + private static let fileDragTypes = + [ + NSPasteboard.PasteboardType.fileURL, + NSPasteboard.PasteboardType("NSFilenamesPboardType") + ] + + NSFilePromiseReceiver.readableDraggedTypes.map { + NSPasteboard.PasteboardType($0) + } + + override init(frame: NSRect, configuration: WKWebViewConfiguration) { + super.init(frame: frame, configuration: configuration) + registerForDraggedTypes(Self.fileDragTypes) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + registerForDraggedTypes(Self.fileDragTypes) + } +} diff --git a/native/macos/Sources/DesktopWebView/WebWindow.swift b/native/macos/Sources/DesktopWebView/WebWindow.swift index f2b433b..a020730 100644 --- a/native/macos/Sources/DesktopWebView/WebWindow.swift +++ b/native/macos/Sources/DesktopWebView/WebWindow.swift @@ -38,7 +38,7 @@ final class WebWindowController: NSObject, NSWindowDelegate, WKUIDelegate, WKNav if #available(macOS 14.0, *) { // media capture handled via UI delegate } - let wv = WKWebView(frame: rect, configuration: config) + let wv = FileDropWebView(frame: rect, configuration: config) wv.allowsBackForwardNavigationGestures = true self.webView = wv super.init() @@ -53,7 +53,7 @@ final class WebWindowController: NSObject, NSWindowDelegate, WKUIDelegate, WKNav let config = WKWebViewConfiguration() config.mediaTypesRequiringUserActionForPlayback = [] let frame = webView.frame - let wv = WKWebView(frame: frame, configuration: config) + let wv = FileDropWebView(frame: frame, configuration: config) wv.uiDelegate = self wv.navigationDelegate = self window.contentView = wv diff --git a/native/windows/README.md b/native/windows/README.md index 940bfa0..280fcee 100644 --- a/native/windows/README.md +++ b/native/windows/README.md @@ -33,7 +33,7 @@ $env:DESKTOP_WEBVIEW_BINARY = "$PWD\priv\native\windows\DesktopWebView.exe" mix test.e2e ``` -## HTML file inputs +## HTML file inputs and Explorer drops Normal HTML file inputs use WebView2's built-in Windows file picker: @@ -42,11 +42,16 @@ Normal HTML file inputs use WebView2's built-in Windows file picker: - If the user cancels, the input receives no new files and the page receives no new selection. - No host C++ change, JSON-RPC call, CDP interception, or custom picker is needed. The page reads selected files through the normal HTML `input.files` API. +File-manager drops are also required. Preserve WebView2's normal drag handling +so Explorer file drops reach the page as a `drop` event with +`dataTransfer.files`. Do not replace page drops with `dialog.choose_file` or a +host-only drop handler. + The picker needs an interactive Windows desktop and a working Microsoft Edge WebView2 Evergreen Runtime. It cannot show from a service, a headless run, or a non-interactive session. Runtime or Windows security restrictions can also prevent native UI from appearing, so test with a current runtime in a normal -desktop session. +desktop session. Test Explorer drops in the same session. This browser feature is separate from the [`dialog.choose_file` JSON-RPC method](../../docs/protocol.md#dialog). That method is an explicit host dialog