0

I have the following Javascript script:

async function getIPAddress() {
    var thisFuncName = arguments.callee.toString().match(/function ([^\(]+)/)[1];
    var retValue = {
        'Result': true,
        'ResultText': '',
        'Message': '',
        'ScriptFuncName': thisFuncName
    };

    try {
        const response = await fetch('https://api.ipify.org?format=json');
        if (response.ok) {
            const data = await response.json();
            console.log(`=> ${JSON.stringify(data)}`);
            retValue.ResultText = data.ip;
            return retValue;
        } else {
            throw new Error('Failed to fetch data');
        }
    } catch (error) {
        console.error('Error:', error);
        retValue.Message = `Error: ${error}`
        retValue.Result = false;
        return retValue;
    }
}
await getIPAddress();

which works fine when used in MS Edge browser's DevTools:

Running script in a browser devtools

But when I'm trying to execute this script using MS WebView2 control's .ExecuteScriptAsync(...) method then I'm getting null, and when I'm trying to execute this script using WebView2.DevTools.Dom's EvaluateFunctionAsync(...) method then I'm getting error Evaluation failed: SyntaxError: Unexpected reserved word.

10
  • "when I'm trying to execute this script"... how, exactly? Commented Apr 29 at 1:46
  • Do you mean MS WebView2's .ExecuteScriptAsync(...) method, or WebView2.DevTools.Dom's EvaluateFunctionAsync(...)? For the former I'm using "standard" approach, which works well with the non-async scripts, for the latter, I have tried to adapt the code from the Evaluate Javascript section located at the bottom of the web page with this link: the nuget.org/packages/WebView2.DevTools.Dom => var someObject = await devToolsContext.EvaluateFunctionAsync<dynamic>("{my script text is loaded here from file}");... Commented Apr 29 at 2:06
  • ... I have also tried to "de-Promise" the script by replacing await getIPAddress(); with Promise.resolve(await getIPAddress()); - this version of script also works in DevTools but EvaluateFunctionAsync(...) returns error message Evaluation failed: SyntaxError: Unexpected identifier 'Promise'. Obviously if this approach could somehow work for the latter case then the syntactic construction should be different. Commented Apr 29 at 2:07
  • github.com/MicrosoftEdge/WebView2Feedback/issues/2295 Commented Apr 29 at 2:26
  • 1
    Well, you just call window.chrome.webview.postMessage() from the JavaScript. Example here: How do I get YouTube native player to allow full screen in WebView2? and here: Which WebView2 event function can be use to replace ScriptNotify Webview event? (slightly different language) Commented Apr 29 at 12:02

1 Answer 1

0

Using helpful comments and hints from Phil and Jimi I have compiled a generic solution for my original Javascript script. Here I'm posting a bit more simplified but fully functional solution. The software versions I've used for the solution are .NET 9 (.NET Runtimes 9.0.4, .NET SDK 9.0.201) and Microsoft.Web.WebView2 v.1.0.3179.45.

  1. Solution code

    public static async Task<string> ExecuteAsyncJSScriptAsync(
               Microsoft.Web.WebView2.WinForms.WebView2 wv,
               string jsScript,
               Func<string, Task> log,
               int timeOutInSeconds = 10)
    {
        var sw = Stopwatch.StartNew();
    
        ManualResetEvent waitFlag = new ManualResetEvent(false);
        var ipAddress = "";
        var timeOut = false;
        var timeOutTimer = new System.Timers.Timer(timeOutInSeconds)
        {
            Interval = timeOutInSeconds * 1000
        };
        timeOutTimer.Elapsed += new System.Timers.ElapsedEventHandler((sender, e) =>
        {
            timeOut = true;
            timeOutTimer.Stop();
            waitFlag.Set();
        });
        timeOutTimer.Start();
    
        if (!wv.CoreWebView2.Settings.IsWebMessageEnabled) wv.CoreWebView2.Settings.IsWebMessageEnabled = true;
        if (!wv.CoreWebView2.Settings.IsScriptEnabled) wv.CoreWebView2.Settings.IsScriptEnabled = true;
    
        var webMessageReceiveEventHandler = new EventHandler<CoreWebView2WebMessageReceivedEventArgs>(
                async (sender, args) =>
                {
                    var ipMessagePrefix = "ip => ";
                    var message = args.TryGetWebMessageAsString();
                    if (message.StartsWith(ipMessagePrefix))
                    {
                        await log($"webMessageReceived: IP found = {message}");
                        ipAddress = message.Substring(ipMessagePrefix.Length);
                    }
                    else
                    {
                        await log($"webMessageReceived: Message = {message}");
                    }
    
                    waitFlag.Set();
                }
                );
    
        try
        {
            wv.WebMessageReceived += webMessageReceiveEventHandler;
    
            await wv.ExecuteScriptAsync(jsScript);
    
            await log($"async JS execution activated, non-blocking wait started, elapsed = {sw.Elapsed.TotalSeconds:0.000}s");
    
            await Task.Run(() => waitFlag.WaitOne());
    
            await log($"Non-blocking wait completed, elapsed = {sw.Elapsed.TotalSeconds:0.000}s");
        }
        finally
        {
            wv.WebMessageReceived -= webMessageReceiveEventHandler;
            timeOutTimer.Dispose();
            waitFlag.Dispose();
        }
    
        await log($"ExecuteAsyncJSScriptAsync completed, elapsed = {sw.Elapsed.TotalSeconds:0.000}s");
    
        if (timeOut)
        {
            var errorMessage = "Error: Failed to execute async JS script - time-out";
            await log(errorMessage);
            return errorMessage;
        }
    
        return ipAddress;
    }
    
  2. GetIPAddress JS Script

Hint: to make window.chrome.webview.postMessage(...) fired on C# client code side the JS Script should be "wrapped" into Promise.resolve(...).

    private string getIPAddressJsScript =>
    @"
        Promise.resolve(
        (async function getIPAddress() {
            data = await (await fetch('https://api.ipify.org?format=json')).json();
            ip = data.ip;
            window.chrome.webview.postMessage(`ip => ${ip}`);
            return ip;
        })());
    ";
  1. GetIPAddress method

    public async Task<string> GetIPAddress(
                 Microsoft.Web.WebView2.WinForms.WebView2 wv, 
                 Func<string, Task> log)
    {
        return await ExecuteAsyncJSScriptAsync(wv, getIPAddressJsScript, log);
    }
    
  2. Test run log

  async JS execution activated, non-blocking wait started, elapsed = 0.001s
  webMessageReceived: IP found = 119.13.224.44
  Non-blocking wait completed, elapsed = 0.662s
  ExecuteAsyncJSScriptAsync completed, elapsed = 0.664s

Please comment on this solution to improve it.

P.S. Of course WebView2 has to be initialized before using the solution code, e.g:

 await webView2InstanceRef.EnsureCoreWebView2Async(
     await CoreWebView2Environment.CreateAsync(null, null));
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.