Skip to content

macOS: propose native file-download support (WKDownload) for NativeWebView #83

Description

@szemeredipeter-prog

Summary

Right now, when a NativeWebView on macOS navigates into a non-HTML response (a file/PDF/image the page offers for download), there is no download handling at all - the WebView just tries to render the raw response in place. #82 fixes the resulting crash (an unhandled JavaScriptException from the bridge injection step), but even with that fix, the user still does not get an actual downloaded file anywhere - the page just navigates into the raw content instead.

This issue proposes closing that gap with WebKit's own native download API (WKDownload, macOS 11.3+/iOS 14.5+), rather than any app-level workaround.

Relationship to #82

This is a follow-up to #82, which this depends on conceptually (both touch the same decidePolicyForNavigation* delegate surface) but not code-wise - #82 is a minimal crash fix; this is a separate feature addition, and I would expect it to land as its own PR after #82, not bundled with it.

Proposed approach

Use WKNavigationResponse/WKDownload, the same native API WebKit itself exposes for this on macOS 11.3+ / iOS 14.5+, instead of trying to infer "this is a download" from a URL or racing the navigation:

  • Implement webView:decidePolicyForNavigationResponse:decisionHandler: on the existing WKNavigationDelegate wrapper, returning .download when the response either is not renderable (!response.canShowMIMEType) or explicitly says so (Content-Disposition: attachment).
  • This check only applies to actual navigations (main/sub-frame document loads) - ordinary sub-resource fetches (XHR, images, scripts, stylesheets) never reach this delegate method, so it cannot misfire on regular page traffic.
  • Because the response becomes a download before WebKit would otherwise replace the page with the raw file content, the currently-loaded page is never touched/navigated away from.
  • Implement WKDownloadDelegate (decideDestinationUsingResponse:suggestedFilename:completionHandler:, downloadDidFinish:, download:didFailWithError:...) to pick a destination and report the outcome.

What we would need maintainer input on before writing a PR

This is as much an API-design question as an implementation one, so I am opening this as an issue rather than sending a PR outright - I do not want to presume the answers to things like:

  • What should the app-facing API look like? (e.g. new DownloadStarting/DownloadCompleted/DownloadFailed events on NativeWebView/the adapter interfaces, vs. something else)
  • Should destination-path selection be delegated to app code (a cancellable "choose where to save" event), or should the control just always save to a sensible default (e.g. the platform Downloads folder) with collision-avoidance, the way browsers do?
  • Is there an existing convention elsewhere in this codebase (Windows/WebView2 side, if it has download support already) this should follow for consistency across platforms?
  • Filename/collision handling: is name (1).ext-style renaming (what our PoC does) the expected convention, or should it be configurable?

What is already validated (not submitted as code here - by design, see above)

We built and tested a working version of this to make sure the approach is sound before proposing it:

New interop files (would live under Avalonia.Controls.WebView.Core/Macios/Interop/WebKit/):

  • NSURLResponse.cs - MIMEType, SuggestedFilename, HasAttachmentContentDisposition()
  • WKNavigationResponse.cs - Response, CanShowMIMEType, ForMainFrame
  • WKDownload.cs - SetDelegate()
  • WKDownloadDelegate.cs - the ObjC delegate subclass

Modified (same hand-rolled Objective-C runtime bridge style already used elsewhere in Avalonia.Controls.WebView.Core/Macios, no new dependency):

  • Interop/NSUrl.cs - FromFilePath() (+fileURLWithPath:)
  • Interop/WebKit/WKNavigationDelegate.cs - the two delegate methods above, plus a WKNavigationResponsePolicy enum
  • MaciosWebViewAdapter.cs - wiring + the destination-path/completion logic

Test results:

  • Isolated repro (local HTTP server, controlled Content-Disposition: attachment response, Accessibility-API-driven click, not a synthetic event): no crash, file created with byte-for-byte correct content, page stayed loaded, file opens via Launch Services, filename collision-avoidance confirmed on a repeat download.
  • Human test with real ChatGPT-generated files (.md x2, .json, .py): no crash, all files landed in the Downloads folder with sensible names, collision-avoidance worked on a repeat.

Happy to open a PR with this implementation once there is agreement on the API surface above - or to adjust the approach based on whatever direction you would prefer instead.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions