ReviewOS

craft-native/craft

<input type="file"> is inert: no runOpenPanel WKUIDelegate, so no file picker ever opens

#400
Closed chris opened this 8 days ago ·
8 days ago

<input type="file"> does nothing in a Craft window. Clicking one — or calling .click() on a hidden one, which is the common pattern behind an "Attach image" button — opens no picker, raises no error, and logs nothing to the console or to stderr. The page has no way to tell that it failed.

Cause

WKWebView does not open a file picker itself. It asks its WKUIDelegate for one, through:

- webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:

If the delegate does not implement that method, WKWebView silently does nothing. Craft does not implement it. These are every webView: selector registered across packages/zig/src/:

webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:
webView:decidePolicyForNavigationAction:decisionHandler:
webView:decidePolicyForNavigationResponse:decisionHandler:
webView:didCommitNavigation:
webView:didReceiveAuthenticationChallenge:completionHandler:
webView:requestMediaCapturePermissionForOrigin:initiatedByFrame:type:decisionHandler:

runOpenPanel... is absent, so every file input in every Craft window is inert.

Worth noting the media-capture delegate is right there at macos.zig:6546 — camera and microphone prompts work. File selection is the one input path that was not wired.

The pieces already exist

system.zig already drives NSOpenPanel for the scripting API:

  • system.zig:405 — pub fn selectDirectory(options: FileDialog)
  • system.zig:419 — objc.objc_getClass("NSOpenPanel")

So this is not new platform work. It is adding the delegate method alongside the one at macos.zig:6546, opening a panel the way selectDirectory already does, and handing the chosen URLs back through the completion handler.

The parameters object carries what the panel should reflect: allowsMultipleSelection, and on recent macOS allowedFileExtensions / allowsDirectories. Honouring allowsMultipleSelection matters — a page that sets multiple and receives one file has no way to know why.

Calling the completion handler with nil when the user cancels is what lets the page's change event stay unfired, which is the behaviour a browser gives.

Reproducing

Any page with a file input, in any Craft window:

<input type="file" accept="image/*">
craft https://example.test/with-a-file-input --titlebar-hidden

Click it. Nothing happens. Same result for the hidden-input pattern:

const input = document.querySelector('input[type=file]')
input.click()   // no panel, no error, no rejected promise

Observed on craft 0.0.294, macOS, with a locally built zig-out/bin/craft.

Why it is worth fixing rather than documenting

An app shipping through the Mac App Store cannot ask its users to "attach an image in the browser version instead". And the failure mode is the bad kind: the markup is correct, it works when the same page is opened in Safari, and nothing anywhere says why it does not work here — so the time goes into the app's own code first. That is where I spent it before finding this.

Sign in to comment on this issue.