Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] HybridWebView crash app #28801

Open
michalpobuta opened this issue Apr 4, 2025 · 0 comments
Open

[Android] HybridWebView crash app #28801

michalpobuta opened this issue Apr 4, 2025 · 0 comments
Labels
area-controls-hybridwebview HybridWebView control platform/android 🤖 t/bug Something isn't working

Comments

@michalpobuta
Copy link
Contributor

Description

I am using TradingView inside HybridWebView. I noticed that sometimes when I go back (from page that have this view) my app crash.

I started to look for a problem in MAUI code, and I found it XD

So HybridWebViewHandler for Android is using MauiHybridWebViewClient. Most interesting method for us is ShouldInterceptRequest which use GetResponseStream.

Inside this method we have a case for InvokeDotNetPath

// 1. Try special InvokeDotNet path
if (relativePath == HybridWebViewHandler.InvokeDotNetPath)
{
    var fullUri = new Uri(fullUrl!);
    var invokeQueryString = HttpUtility.ParseQueryString(fullUri.Query);
    var contentBytesTask = Handler.InvokeDotNetAsync(invokeQueryString);
    var responseStream = new DotNetInvokeAsyncStream(contentBytesTask, Handler);
    return new WebResourceResponse("application/json", "UTF-8", 200, "OK", GetHeaders("application/json"), responseStream);
}

Okey, now lets look inside constructor of DotNetInvokeAsyncStream which is private class inside this class and is implementation of Stream

public DotNetInvokeAsyncStream(Task<byte[]?> invokeTask, HybridWebViewHandler handler)
{
    _task = invokeTask;
    _handler = new(handler);
    _pipe = new Pipe(new PipeOptions(
	pauseWriterThreshold: PauseThreshold,
	resumeWriterThreshold: ResumeThreshold,
	useSynchronizationContext: false));
    InvokeMethodAndWriteBytes();
}

Looks good ? Then lets look inside InvokeMethodAndWriteBytes()

private async void InvokeMethodAndWriteBytes()
{
    try
    {
        var data = await _task;

        // the stream or handler may be disposed after the method completes
        ObjectDisposedException.ThrowIf(_isDisposed, nameof(DotNetInvokeAsyncStream));
        ArgumentNullException.ThrowIfNull(Handler, nameof(Handler));

        // copy the data into the pipe
        if (data is not null && data.Length > 0)
        {
            var memory = _pipe.Writer.GetMemory(data.Length);
            data.CopyTo(memory);
            _pipe.Writer.Advance(data.Length);
        }

        _pipe.Writer.Complete();
    }
    catch (Exception ex)
    {
        Handler?.MauiContext?.CreateLogger<HybridWebViewHandler>()?.LogError(ex, "Error invoking .NET method from JavaScript: {ErrorMessage}", ex.Message);

        _pipe.Writer.Complete(ex);
    }
}

Really, async void? Here we have first mistake. Lets go to another - what if we are on page with HWV and go to another when the _task is running ? In other words, what if we disconnect hander during doing _task?
As we can see in the code ArgumentNullException.ThrowIfNull(Handler, nameof(Handler)) will throw exception then we write exception to pipe _pipe.Writer.Complete(ex). This actually seems fine, but lets go back to InvokeDotNetPath

var responseStream = new DotNetInvokeAsyncStream(contentBytesTask, Handler);
    return new WebResourceResponse("application/json", "UTF-8", 200, "OK", GetHeaders("application/json"), responseStream);

So responseStream return stream with Exception inside and then pass it to WebResourceResponse. But what WebResourceResponse actually do?

[Register(".ctor", "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/util/Map;Ljava/io/InputStream;)V", "")]
    public WebResourceResponse(
      string? mimeType,
      string? encoding,
      int statusCode,
      string reasonPhrase,
      IDictionary<string, string>? responseHeaders,
      Stream? data);

Ohh, so we pass stream with exception to Android API.... And what this cause ? A native exception that we can't handle so app crash.

System.ArgumentNullException: ArgumentNull_Generic Arg_ParamName_Name, Handler-> at Microsoft.Maui.Platform.MauiHybridWebViewClient.DotNetInvokeAsyncStream.InvokeMethodAndWriteBytes()-> at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& )-> at System.IO.Pipelines.Pipe.ReadAsync(CancellationToken )-> at System.IO.Pipelines.Pipe.DefaultPipeReader.ReadAsync(CancellationToken )-> at Microsoft.Maui.Platform.MauiHybridWebViewClient.DotNetInvokeAsyncStream.Read(Byte[] buffer, Int32 offset, Int32 count)-> at Android.Runtime.InputStreamAdapter.Read(Byte[] bytes, Int32 offset, Int32 length)-> at Java.IO.InputStream.n_Read_arrayBII(IntPtr jnienv, IntPtr native__this, IntPtr native_b, Int32 off, Int32 len)-> at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPLII_I(_JniMarshal_PPLII_I callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, Int32 p1, Int32 p2)

Please, re write this class, because this code is some kind of mistake.

Steps to Reproduce

I didn't do any minimal version, but i guess if you delay that task, and perform go back this will happened

Link to public reproduction project repository

No response

Version with bug

9.0.50 SR5

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

No response

Did you find any workaround?

No, but i guess that returning _pipe.Writer.Complete(); instead of _pipe.Writer.Complete(ex); will do.

Relevant log output

System.ArgumentNullException: ArgumentNull_Generic Arg_ParamName_Name, Handler-> at Microsoft.Maui.Platform.MauiHybridWebViewClient.DotNetInvokeAsyncStream.InvokeMethodAndWriteBytes()-> at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& )-> at System.IO.Pipelines.Pipe.ReadAsync(CancellationToken )-> at System.IO.Pipelines.Pipe.DefaultPipeReader.ReadAsync(CancellationToken )-> at Microsoft.Maui.Platform.MauiHybridWebViewClient.DotNetInvokeAsyncStream.Read(Byte[] buffer, Int32 offset, Int32 count)-> at Android.Runtime.InputStreamAdapter.Read(Byte[] bytes, Int32 offset, Int32 length)-> at Java.IO.InputStream.n_Read_arrayBII(IntPtr jnienv, IntPtr native__this, IntPtr native_b, Int32 off, Int32 len)-> at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPLII_I(_JniMarshal_PPLII_I callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, Int32 p1, Int32 p2)
@michalpobuta michalpobuta added the t/bug Something isn't working label Apr 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-controls-hybridwebview HybridWebView control platform/android 🤖 t/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants