Skip to content

macOS: unhandled JavaScriptException in async void OnDelegateOnDidFinishNavigation crashes the whole process on non-scriptable navigation (e.g. file download) #81

Description

@szemeredipeter-prog

Describe the bug

On macOS (Apple Silicon), when the WKWebView backend used by Avalonia.Controls.WebView navigates to a URL whose response is not a scriptable HTML document, the whole process can crash with an unhandled Avalonia.Controls.JavaScriptException.

Observed with ChatGPT-generated file download links. The same application code using the Windows WebView2 backend does not crash on the equivalent download.

The crash occurs inside the macOS adapter. MaciosWebViewAdapter.OnDelegateOnDidFinishNavigation unconditionally calls InvokeScript(...) after navigation finishes. If JavaScript cannot be executed in the resulting document, InvokeScript rethrows the native WebKit failure as JavaScriptException. Because the navigation completion handler is async void and does not catch that exception, the exception escapes and terminates the process.

Reproduced 3 times across two test sessions with an identical stack trace. Application-level attempts to cancel navigation, intercept only NewWindowRequested, and run with no custom navigation handlers did not prevent the crash.

To Reproduce

  1. Run an Avalonia application on macOS Apple Silicon with a NativeWebView.
  2. Navigate to a page containing a downloadable file link.
  3. Click a link whose response is a file/download response rather than a normal scriptable HTML page.
  4. Observe that the process exits with an unhandled Avalonia.Controls.JavaScriptException.

Observed in practice with ChatGPT-generated downloads.

A standalone example target would be an <a href="/download"> where /download responds with:

  • Content-Type: application/octet-stream
  • Content-Disposition: attachment; filename=test.txt

Expected behavior

Navigating to or initiating a file download must not crash the entire application process.

At minimum, failure to inject the C#↔JavaScript bridge into a non-scriptable document should be handled gracefully.

Actual native file-download support can be treated as a separate feature/fix.

Avalonia WebView version

12.1.0

Avalonia version

12.1.1

OS

macOS

Additional context

Environment:

  • macOS, Apple Silicon (osx-arm64)
  • Target framework: net8.0
  • dotnet publish -r osx-arm64 -c Debug --self-contained
  • Also reproduces in Release
  • .NET SDK used to build: 10.0.400
  • Windows/WebView2 backend, same app code: does not crash on the equivalent download

Stack trace:

Unhandled exception. Avalonia.Controls.JavaScriptException: Cannot execute JavaScript in this document
 ---> Avalonia.Controls.Macios.Interop.NSErrorException: A JavaScript exception occurred
   at Avalonia.Controls.Macios.Interop.WebKit.WKWebView.EvaluateJavaScriptAsync(String script)
   at Avalonia.Controls.Macios.MaciosWebViewAdapter.InvokeScript(String script)
   at Avalonia.Controls.Macios.MaciosWebViewAdapter.OnDelegateOnDidFinishNavigation(Object sender, EventArgs args)
   ...

Source-level confirmation:

private async void OnDelegateOnDidFinishNavigation(object? sender, EventArgs args)
{
    _ = await InvokeScript(WebViewHelper.BuildWebKitInvokeCSharpActionScript(_scriptHandlerMessageName, stringify: true));

    using var url = _webView.Url;
    NavigationCompleted?.Invoke(this, new WebViewNavigationCompletedEventArgs { Request = Uri.TryCreate(url!.AbsoluteString, UriKind.Absolute, out var uri) ? uri : null, IsSuccess = true });
}

InvokeScript converts the native WebKit JavaScript failure into JavaScriptException, and the async void completion handler does not catch it.

Suggested crash-safety fix:

private async void OnDelegateOnDidFinishNavigation(object? sender, EventArgs args)
{
    try
    {
        _ = await InvokeScript(WebViewHelper.BuildWebKitInvokeCSharpActionScript(_scriptHandlerMessageName, stringify: true));
    }
    catch (JavaScriptException)
    {
        // Bridge injection failure must not terminate the process.
    }

    using var url = _webView.Url;
    NavigationCompleted?.Invoke(this, new WebViewNavigationCompletedEventArgs { Request = Uri.TryCreate(url!.AbsoluteString, UriKind.Absolute, out var uri) ? uri : null, IsSuccess = true });
}

This proposed change is intentionally scoped only to preventing the process crash. Full download support is separate.

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

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions