MicrosoftEdge / WebView2Feedback

Feedback and discussions about Microsoft Edge WebView2
https://aka.ms/webview2
455 stars 55 forks source link

[Problem/Bug]: Inconsistent behavior for HostObject method with optional parameter called with upper/lower case first letter #4510

Open Mirzek opened 7 months ago

Mirzek commented 7 months ago

What happened?

The bug was found in more complex project, but I reproduced it in the most simple way possible in separate clear WPF project. I added only WebView2 there and added HostObject by calling AddHostObjectToScript with code below:

...
        Loaded="Window_Loaded">
    <Grid>
        <wv2:WebView2 x:Name="webView2" Source="https://google.com/" />
    </Grid>
</Window>
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
    await webView2.EnsureCoreWebView2Async();
    webView2.CoreWebView2.AddHostObjectToScript("bridge", new BridgeHostObject());
}

To explore all basic possibilities the BridgeHostObject has following two methods:

[ComVisible(true)]
public class BridgeHostObject
{
    public string? TestMethodA(string? str)
    {
        return str;
    }

    public string? TestMethodB(string? str = null)
    {
        return str;
    }
}

I found out about the bug as we are moving an application from CefSharp to WebView2 and CefSharp supported calling HostObject methods with any first letter from JavaScript side so it was causing errors after switching to WebView2. However thanks to that I also found about following weird inconsistent behavior.

When calling methods with JavaScript using upper case first letter the behavior is as expected.

WebView2Issue-Screenshot01

When calling methods with JavaScript using lower case first letter the behavior is following where in case of TestMethodA the break point on the side of WPF application is not activated, but in case of TestMethodB it is and its parameter is always null even when value 'abcd' was entered from the side of the JavaScript.

WebView2Issue-Screenshot02

WebView2Issue-Screenshot03

Importance

Moderate. My app's user experience is affected, but still usable.

Runtime Channel

Stable release (WebView2 Runtime)

Runtime Version

123.0.2420.47

SDK Version

1.0.2420.47

Framework

WPF

Operating System

Windows 11

OS Version

Edition Windows 11 Enterprise Version 22H2 Installed on ‎11. ‎4. ‎2024 OS build 22621.3447 Experience Windows Feature Experience Pack 1000.22688.1000.0

Repro steps

  1. Create simple application with the code in the description above
  2. Put breakpoints in both TestMethodA and TestMethodB
  3. Start the application
  4. Right click the WebView2 element and go to Inspect->Console
  5. Run await chrome.webview.hostObjects.bridge.testMethodA('abcd')
  6. No breakpoint is activated as expected
  7. Run await chrome.webview.hostObjects.bridge.testMethodB('abcd')

Expected behavior: No breakpoint activated as well

Actual behavior: Breakpoint for TestMethodB activated and the parameter str having value null instead of "abcd" and as the code was successfully executed and null was returned at least seeing that in Console would be expected, but not even that happened as "Unable to call method on non-function" error message was shown instead.

Repros in Edge Browser

No, issue does not reproduce in the corresponding Edge version

Regression

Don't know

Last working version (if regression)

No response

LiangTheDev commented 7 months ago

The behavior depends on how .NET implements with IDispatch witch WebView2 uses to interact with host objects. Which version of .NET are you using? In my project targeting .NET Framework 4.6 (net462, with LangVersion set to be 8.0 so that I can get nullable reference support), calling from JS with lower case method name testMethodB actually it actually worked the same way as TestMethodB.

Mirzek commented 7 months ago

I was using .NET 8.0, but it doesn't seem dependant on the version of the .NET as I recreated the project in .NET Framework 4.6.2 and it behaves exactly the same in every way I described above. I only changed the string to not have explicit nullable reference as it is nullable implicitly in .NET Framework:

[ComVisible(true)]
public class BridgeHostObject
{
    public string TestMethodA(string str)
    {
        return str;
    }

    public string TestMethodB(string str = null)
    {
        return str;
    }
}
LiangTheDev commented 7 months ago

It is the known issue with .NET core interaction. The issue repros with .NET core but not legacy .NET Framework. No easy fix from WebView2 side.

The hit of TestMethodB actually was due to WebView2 code tries to figure out whether testMethodB is a property by invoking the IDispatch object with DISPATCH_PROPERTYGET.

This is similar to https://github.com/MicrosoftEdge/WebView2Feedback/issues/2840#issuecomment-1284347447.

Mirzek commented 7 months ago

Once again. I tried it also in .NET Framework 4.6.2 and it behaves exactly the same, so no it does not seem to be issue only with .NET core. However if there is no easy fix from WebView2 side I guess that is the answer anyway, but I just wanted to clarify that using .NET Framework 4.6.2 is not a workaround as it doesn't work there as well.

LiangTheDev commented 7 months ago

Figured out why it didn't repro for me with .NET Framework and repro for you. I also have [ClassInterface(ClassInterfaceType.AutoDual)] on the class. If you add that to the class, it should work when running with .NET Framework:

    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]
    public class BridgeHostObject
Mirzek commented 7 months ago

Yes, with that AutoDual attribute it now works fine both with lower case and upper case first letter with .NET Framework for me as well. Thank you.

Mirzek commented 7 months ago

I tested it again in actual real application and it still didn't work (btw we had AutoDual there from the start and it is .NET Framework application) and I found out it doesn't work even in .NET Framework and AutoDual when the methods are async.

With following code it behaves once again exactly the same as my original description above:

[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public class BridgeHostObject
{
    public async Task<string> TestMethodA(string str)
    {
        return str;
    }

    public async Task<string> TestMethodB(string str = null)
    {
        return str;
    }
}
LiangTheDev commented 6 months ago

It seems that the .NET's type info for the IDispatch doesn't contain the async methods in its member list. We do use some helper that tries to use .NET reflection to figure out whether it is a method. However, that requires exact matching of the name.

Unfortunately, there is no much that we could do in this case.