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.
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;
}
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;
})());
";
GetIPAddress method
public async Task<string> GetIPAddress(
Microsoft.Web.WebView2.WinForms.WebView2 wv,
Func<string, Task> log)
{
return await ExecuteAsyncJSScriptAsync(wv, getIPAddressJsScript, log);
}
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));
.ExecuteScriptAsync(...) method, or WebView2.DevTools.Dom'sEvaluateFunctionAsync(...)? 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}");...await getIPAddress();withPromise.resolve(await getIPAddress());- this version of script also works inDevToolsbutEvaluateFunctionAsync(...)returns error messageEvaluation failed: SyntaxError: Unexpected identifier 'Promise'. Obviously if this approach could somehow work for the latter case then the syntactic construction should be different.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)