Skip to content
Merged
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
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<input type="file">` 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 `<input type="file">` 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)

Expand Down
20 changes: 11 additions & 9 deletions docs/porting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

`<input type="file">` 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.
`<input type="file">` 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

Expand Down
18 changes: 18 additions & 0 deletions docs/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion docs/status/linux.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<input type=file>` | partial | WebKitGTK's asynchronous `run-file-chooser` default handler; native selection and cancellation checks pending |
| HTML `<input type=file>` 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 |
2 changes: 1 addition & 1 deletion docs/status/macos.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<input type=file>` | partial | `WKUIDelegate` open-panel hook; native selection and cancellation checks pending |
| HTML `<input type=file>` 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` |
Expand Down
2 changes: 1 addition & 1 deletion docs/status/windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<input type=file>` | partial | WebView2's built-in picker; native selection and cancellation checks pending |
| HTML `<input type=file>` 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 |
Expand Down
10 changes: 7 additions & 3 deletions native/linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
`<input type="file">`. 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

Expand Down
12 changes: 7 additions & 5 deletions native/macos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ Output is copied to `priv/native/macos/DesktopWebView` (universal when both arch
# prints: listening <port>
```

## HTML file inputs
## HTML file inputs and Finder drops

`<input type="file">` 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

Expand Down
23 changes: 23 additions & 0 deletions native/macos/Sources/DesktopWebView/FileDropWebView.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
4 changes: 2 additions & 2 deletions native/macos/Sources/DesktopWebView/WebWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down
9 changes: 7 additions & 2 deletions native/windows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
Expand Down
Loading