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.
Summary
Right now, when a
NativeWebViewon 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 unhandledJavaScriptExceptionfrom 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:webView:decidePolicyForNavigationResponse:decisionHandler:on the existingWKNavigationDelegatewrapper, returning.downloadwhen the response either is not renderable (!response.canShowMIMEType) or explicitly says so (Content-Disposition: attachment).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:
DownloadStarting/DownloadCompleted/DownloadFailedevents onNativeWebView/the adapter interfaces, vs. something else)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,ForMainFrameWKDownload.cs-SetDelegate()WKDownloadDelegate.cs- the ObjC delegate subclassModified (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 aWKNavigationResponsePolicyenumMaciosWebViewAdapter.cs- wiring + the destination-path/completion logicTest results:
Content-Disposition: attachmentresponse, 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..mdx2,.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.