dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.26k stars 1.76k forks source link

Full list of bugs and issues with new .NET 9 HybridWebView #25804

Open jonmdev opened 1 week ago

jonmdev commented 1 week ago

Description

I spent the full day yesterday struggling to implement the .NET 9 HybridWebView. I wrote two bug reports based on that here and here. But after some rest, I thought it best to try again to better clarify and enumerate all the problems. This may help people as well until the issues are fixed.

1) Javascript functions are forced to always return something

@Eilon you said "The methods can be invoked as async, have optional return values, and optional parameters." However this does not seem to be the case.

If you run my bug project here or make your own, you can play with the functions and set for example in C#:

hybridWebView.InvokeJavaScriptAsync<string>("setBackgroundRandomColor", HybridSampleJSContext.Default.String);

with Javascript:

function setBackgroundRandomColor(){
    let randomColor = getRandomHexColor();
    document.body.style.backgroundColor = randomColor;
}

You will get uncaught exception Message = "'u' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0." which is something I saw all day yesterday. This is obviously not meaningful. This is the error for "no return received from Javascript," at least with string. It seems unintentional as you said your intent was to make returning optional, which I agree it should be. The forced return causes weird design patterns as I explained in my other thread and you will see here also.

If you need to add a new type for InvokeJavaScriptAsync<HybridViewNoReturn> even that would be okay although a bit silly looking perhaps. My feeling is no return should be set by making the second argument null and then simply not awaiting obviously or if awaiting with second argument null it just returns immediately. Or for no return, a single or three argument untyped non-async function like InvokeJavaScript("setBackgroundRandomColor", new object[]{arg}, new[]{ argType});

2) Return from Javascript must be precisely formulated

The only consistent way I could get such functions to return was as follows:

const returnOk = JSON.stringify("okay"); //use at end of every function

function setBackgroundRandomColor(){
    let randomColor = getRandomHexColor();
    document.body.style.backgroundColor = randomColor;
        return returnOk;
}

To "solve" this I thus created wrappers for all my Javascript code like above to access from C# and had them return something generic like this returnOk. But this is also I am sure unintentional based on your quote above stating return should be optional.

If you try return "Okay" this doesn't work, or return "['Okay']" or return '["Okay"]' which say The JSON value could not be converted to System.String. I am not sure of the rules here but using JSON.stringify on all string returns seems to solve it. I don't know how to manually format a string in Javascript to make it return without using stringify. Is there a manually written Javascrript string format that is accepted?

3) Setting the second argument to null fails with an uncaught exception.

This is even if you have no intention of returning anything (and thus the second argument is irrelevant).

You can try this as:

var result = await hybridWebView.InvokeJavaScriptAsync<string>("setBackgroundRandomColor", null);

4) Setting the fourth argument to null (or omitting) fails silently

If you use a function that invokes parameters, but omit the fourth argument (so it defaults to null) but use the first three arguments, it fails silently - the function is not called to my knowledge at all and nothing alerts you of this.

For example if you run this from C# you will get no errors:

List<Color> colors = new (){Colors.DarkBlue, Colors.DarkGreen, Colors.DarkRed};
var color = colors[Random.Shared.Next(0, colors.Count)];
string colorString = color.ToHex();
var result = hybridWebView.InvokeJavaScriptAsync<string>(
    "setBackgroundColor",
    HybridSampleJSContext.Default.String,
    new object[] {colorString}
    //,new[] { HybridSampleJSContext.Default.String }
    );

with Javascript:

function setBackgroundColor(colorString){
    document.body.style.backgroundColor = colorString;
    return JSON.stringify("okay");
}

I did not test further but I imagine similar problems then if the count of objects in parameters and parameter json types are not the same (ie. this should cause a warning but doubt it will - for each parameter need one JSON type that is not null.)

5) Permissions requests cannot be caught in Android

It is no longer possible to catch permissions requests from Android HybridWebView using the available prior method as per: https://github.com/dotnet/maui/issues/25784 This obviously needs a fix.

Unlike the rest of things I mention here I found no workaround to this so it is likely the biggest issue.

6) Key documentation is hidden in GitHub

The only documentation of the new methods are in this thread and this article which are missing a lot of instruction and obviously GitHub is not an easy place to search. The GitHub page I only found by clicking through @Eilon's issue reports. The official Microsoft page needs more work.

7) Documentation needs section on sending raw messages from Javascript to C

It would ideally need a section explaining that the method to send messages from Javascript to C# (has changed names now) is called by the fullest name of window.HybridWebView.sendRawMessage or just HybridWebView.sendRawMessage. This is not explained in the documentation I see. I see there is one line implementing this with Ctrl+F in one line of demo code, but a proper section explaining it would be better.

8) Json Type method needs more explanation

This method is now key to the system. It should be elaborated on what it means, what it is doing, and with more types and their Json Type equivalents. It should also be clarified that this method creates errors in Visual Studio as you type, because these things are generated on build, and those errors disappear after building. I would add to the demo code (with further explanation):

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Dictionary<string, string>))] //becomes "HybridSampleJSContext.Default.DictionaryStringString"
[JsonSerializable(typeof(string))] //becomes HybridSampleJSContext.Default.String
[JsonSerializable(typeof(bool))] //becomes HybridSampleJSContext.Default.Boolean
[JsonSerializable(typeof(float))] //becomes HybridSampleJSContext.Default.Single
[JsonSerializable(typeof(double))] //becomes HybridSampleJSContext.Default.Double
internal partial class HybridSampleJSContext : JsonSerializerContext
{
    // This type's attributes specify JSON serialization info to preserve type structure
    // for trimmed builds.  
}

Those would be the common types needed. None of this is immediately apparent and the logic for the expected names of the types for the created JsonSerializerContext (or how to debug what they will be if any code will tell you) should be better explained, especially given these are only actually created after building.

9) Possible new design pattern?

If there is an intended use case (which I just thought of after yesterday) that we can trigger our own Javascript functions while bypassing all these typing and checking and returning issues (though I still think they should be fixed), this could be explained as that you can run hybridWebView.sendRawMessage("functionName") and then running in Javascript to receive:

        window.addEventListener(
            "HybridWebViewMessageReceived",
            function (e) {
                let message = e.detail.message;
                if (message == "runFunction1"){ window.runFunction1(); }
            });

I am not sure if this would be an intended design pattern but it just occurred to me. One could create their own "interop language" just through the raw messages back and forth. This could provide similar function to the original design again except without the nice automatic parameter handling. We would have to manually parse our parameters out of the raw messages based on our own logic.

Conclusion

I think that's everything. It should have been a very quick conversion but took an entire day because of all the above. Fixing this will save other people the same problems.

Overall, the new system does work well once you understand all the issues above. I believe this change was implemented to fix problems with the .NET 8 method not returning properly or consistently and maybe some things being stripped out on build. If so, it is not a bad way of working. If it is necessary, so be it. It requires more explicit typing and boilerplate.

I do think returning from Javascript should be optional still somehow. Uncaught exceptions must be fixed. Permissions issue must be fixed of course.

Worse case scenario, one can code their own logic to work around all of this system with raw messages back and forth as noted above, though it probably should not be necessary really since this method is good once the bugs and issues above are removed.

Version with bug

9.0.0 GA

Is this a regression from previous behavior?

Yes, this used to work in .NET MAUI

Last version that worked well

8.0.0-preview.1.7762

Affected platforms

iOS, Android, Windows

similar-issues-ai[bot] commented 1 week ago

We've found some similar issues:

If any of the above are duplicates, please consider closing this issue out and adding additional context in the original issue.

Note: You can give me feedback by 👍 or 👎 this comment.

mattleibow commented 1 week ago

Thanks for this detailed feedback! I'll let @Eilon comment and break this one up.