Skip to content

Instantly share code, notes, and snippets.

@productinfo
Forked from R0Wi/Main.cs
Created June 30, 2022 05:11
Show Gist options
  • Save productinfo/7bc3bb42ab7caa0c7b39a34bbf6bf7d1 to your computer and use it in GitHub Desktop.
Save productinfo/7bc3bb42ab7caa0c7b39a34bbf6bf7d1 to your computer and use it in GitHub Desktop.
Simple .NET WebView2 DevTools helper to make it possible to invoke async Javascript code (see https://github.com/MicrosoftEdge/WebView2Feedback/issues/416#issuecomment-1073533384)
public static async Task Main()
{
/*
* Use this codesnippet inside your WinForm/WPF WebView2 application
*/
var wv2 = new WebView2();
await wv2.EnsureCoreWebView2Async();
var devTools = new WebView2DevTools(wv2.CoreWebView2);
// Some result vom Javascript
var result = int.Parse(await devTools.EvaluateAsync("(function() { return Promise.resolve(42); })()"));
// A function without result but some execution time (2 seconds)
await devTools.EvaluateAsync("Promise(resolve => setTimeout(resolve, 2000))");
}
/*
* @copyright Copyright (c) 2022 Robin Windey <ro.windey@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------------
* This helper makes it possible to invoke and await async Javascript code via
* WebView2 DevTools interface to overcome the issue mentioned at
* https://github.com/MicrosoftEdge/WebView2Feedback/issues/416#issuecomment-1073533384
*
* To use this class make sure you have the following NuGet packages installed in your project:
* - https://www.nuget.org/packages/Newtonsoft.Json/
* - https://www.nuget.org/packages/Microsoft.Web.WebView2/
*
* Class is highly inspired by
* - https://docs.microsoft.com/en-us/microsoft-edge/webview2/how-to/chromium-devtools-protocol
* - https://github.com/ChromiumDotNet/WebView2.DevTools.Dom
* - https://github.com/hardkoded/puppeteer-sharp
*/
using System;
using System.Threading.Tasks;
using Microsoft.Web.WebView2.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace MyNamespace
{
/// <summary>
/// Implements the <see cref="EvaluateAsync(string)"/> function via chrome
/// devtools protocol to ensure WebView2 async script results can be awaited.
/// Javascript code is always executed against main window.
/// Highly inspired by Microsoft.Web.WebView2.Core.DevToolsProtocolExtension.DevToolsProtocolHelper.Runtime.EvaluateAsync (https://docs.microsoft.com/en-us/microsoft-edge/webview2/how-to/chromium-devtools-protocol)
/// See also https://github.com/MicrosoftEdge/WebView2Feedback/issues/416#issuecomment-1073533384.
/// </summary>
internal sealed class WebView2DevTools
{
private readonly CoreWebView2 _coreWebView2;
public WebView2DevTools(CoreWebView2 coreWebView2)
{
_coreWebView2 = coreWebView2;
}
#region public
public async Task<object> EvaluateAsync(string jsFunctionCode)
{
var callParams = new EvaluateAsyncParameters { Expression = jsFunctionCode };
var jsonCallParams = JsonConvert.SerializeObject(callParams, GetSerializerSettings());
// https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate
var retValJson = await _coreWebView2.CallDevToolsProtocolMethodAsync("Runtime.evaluate", jsonCallParams);
var retVal = JsonConvert.DeserializeObject<FunctionCallResult>(retValJson);
ThrowJavascriptErrorIfAny(retVal);
return retVal.Result.Value;
}
#endregion
#region private
private JsonSerializerSettings GetSerializerSettings() => new JsonSerializerSettings
{
Formatting = Formatting.None,
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
private static void ThrowJavascriptErrorIfAny(FunctionCallResult functionCallResult)
{
if (functionCallResult.ExceptionDetails == null)
return;
throw new InvalidOperationException($"Javascript error: {functionCallResult.ExceptionDetails.Text}");
}
#endregion
#region Json messages and types
/*
* Types are defined at https://github.com/ChromeDevTools/devtools-protocol/blob/master/json/js_protocol.json#L2760
*/
private class Context
{
public AuxData AuxData { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public string Origin { get; set; }
public string UniqueId { get; set; }
}
private class ExecutionContextCreatedEventArgs : EventArgs
{
public Context Context { get; set; }
}
private class RemoteObject
{
public string Type { get; set; }
public string Subtype { get; set; }
public string ClassName { get; set; }
public object Value { get; set; }
public string UnserializableValue { get; set; }
public string Description { get; set; }
public string ObjectId { get; set; }
}
private class ExceptionDetails
{
public int ExceptionId { get; set; }
public string Text { get; set; }
public int LineNumber { get; set; }
public int ColumnNumber { get; set; }
public string ScriptId { get; set; }
public string Url { get; set; }
public int? ExecutionContextId { get; set; }
}
private class FunctionCallResult
{
public RemoteObject Result { get; set; }
public ExceptionDetails ExceptionDetails { get; set; }
}
private class EvaluateAsyncParameters
{
public string Expression { get; set; }
public string ObjectGroup { get; set; }
public bool? Silent { get; set; }
public int? ContextId { get; set; } // If this param is omitted, the evaluation will be performed in the context of the inspected page.
public bool? ReturnByValue { get; set; } = true;
public bool? AwaitPromise { get; set; } = true;
public bool? UserGesture { get; set; }
}
private class AuxData
{
public string FrameId { get; set; }
public bool IsDefault { get; set; }
public string Type { get; set; }
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment