-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExperimentalCdpScriptExecutor.cs
More file actions
72 lines (67 loc) · 4.29 KB
/
Copy pathExperimentalCdpScriptExecutor.cs
File metadata and controls
72 lines (67 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
using System;
using System.Threading.Tasks;
using Microsoft.Web.WebView2.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace WebToolsDataMonitor
{
// Thrown for a genuine JS-level exception surfaced by CDP's Runtime.evaluate (exceptionDetails present).
// Deliberately a real, catchable .NET exception - unlike CoreWebView2.ExecuteScriptAsync, which
// completes "successfully" with the literal string "null" for both a thrown script exception AND a
// rejected/unresolved top-level Promise, giving RunSingleScriptAsync's catch block (retry/FailureLevel/
// snapshot logic) nothing to react to. That gap is exactly the kind of problem solvable with our own
// code, so this wrapper closes it rather than reproducing it.
public class CoreWebView2ScriptExecutionException : Exception
{
public CoreWebView2ScriptExecutionException(string message) : base(message) { }
}
// Hand-written stand-in for CoreWebView2.ExecuteScriptAsync, built directly on the raw Chrome DevTools
// Protocol (Runtime.evaluate with awaitPromise:true, returnByValue:true) instead of the WebView2 SDK's
// own wrapper. Exists ONLY because that SDK wrapper does not await a script's returned Promise at all -
// see the "Experimental CDP Async Execution" section in README.MD for the full investigation: an
// async/fetch-based script comes back as a bare "{}" (an un-awaited Promise serialized by value) or
// "null" (any top-level `await`), regardless of where the `await`/parens are placed. Verified via a
// standalone WebView2 test harness that CDP's own awaitPromise flag does correctly resolve it.
//
// Deliberately mirrors ExecuteScriptAsync's shape (a single static method taking a CoreWebView2 and a
// script string, returning a Task<string>) and its result FORMAT (a JSON-encoded string - i.e. a JS
// string return value comes back double-JSON-encoded, exactly what UnwrapJsonEncodedString already
// expects) so callers, and everything downstream of them (StatusLineDto, Form1's Manager_DataScraped),
// don't need to know or care which of the two execution paths actually ran a given script. Verified
// byte-for-byte identical output against the real ExecuteScriptAsync for string/JSON-string/number/
// bool/null/object return values in the same standalone test harness.
//
// Gated behind the per-script "UseExperimentalCdpAsync" option (default false, resolved the same
// base/override way as DelaySeconds/RunIndex/FailureLevel - see ParallelScraperManager.
// ResolveUseExperimentalCdpAsync) precisely because most of what's risky about bypassing the official
// SDK method IS addressable with code (result-format parity above, real exceptions instead of silent
// "null"/"{}") - the one risk that genuinely isn't addressable this way is that the Chrome DevTools
// Protocol's method/parameter surface (unlike the versioned CoreWebView2 API) carries no stability
// guarantee across the Chromium versions bundled with future WebView2 Runtime auto-updates. That's a
// real, unfixable-by-us risk, which is exactly why this stays an explicit per-script opt-in rather than
// becoming the default execution path.
public static class ExperimentalCdpScriptExecutor
{
public static async Task<string> ExecuteScriptAsync(CoreWebView2 core, string script)
{
var parameters = new JObject
{
["expression"] = script,
["awaitPromise"] = true,
["returnByValue"] = true
};
string rawCdpResult = await core.CallDevToolsProtocolMethodAsync("Runtime.evaluate", parameters.ToString(Formatting.None));
var parsed = JObject.Parse(rawCdpResult);
var exceptionDetails = parsed["exceptionDetails"];
if (exceptionDetails != null)
{
string message = exceptionDetails["exception"]?["description"]?.ToString()
?? exceptionDetails["text"]?.ToString()
?? "Unknown script exception (CDP Runtime.evaluate)";
throw new CoreWebView2ScriptExecutionException(message);
}
var value = parsed["result"]?["value"];
return value == null ? "null" : value.ToString(Formatting.None);
}
}
}