first commit
This commit is contained in:
commit
761b95080f
404 files changed
+312608
No files matched your search
@@ -0,0 +1,150 @@
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Microsoft.Web.WebView2.WinForms;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace anjianjingling.Browser
|
||||
{
|
||||
/// <summary>
|
||||
/// 内嵌 WebView2 命令桥:把脚本命令同步封送到 UI 线程执行。
|
||||
/// 脚本引擎在后台线程,WebView2 必须在 UI 线程访问。
|
||||
/// </summary>
|
||||
public sealed class WebViewBridge
|
||||
{
|
||||
private readonly WebView2 _web;
|
||||
private const int DefaultTimeoutMs = 30000;
|
||||
|
||||
public WebViewBridge(WebView2 web) { _web = web; }
|
||||
|
||||
private T Invoke<T>(Func<Task<T>> func, int timeoutMs = DefaultTimeoutMs)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
_web.BeginInvoke(async () =>
|
||||
{
|
||||
try { tcs.TrySetResult(await func()); }
|
||||
catch (Exception ex) { tcs.TrySetException(ex); }
|
||||
});
|
||||
if (!tcs.Task.Wait(timeoutMs))
|
||||
throw new TimeoutException("浏览器操作超时");
|
||||
return tcs.Task.Result;
|
||||
}
|
||||
|
||||
private void InvokeAction(Func<Task> func, int timeoutMs = DefaultTimeoutMs)
|
||||
{
|
||||
Invoke(async () => { await func(); return true; }, timeoutMs);
|
||||
}
|
||||
|
||||
/// <summary>ExecuteScriptAsync 返回的是 JSON 字符串,解码为可读文本。</summary>
|
||||
private static string DecodeJsResult(string json)
|
||||
{
|
||||
if (json == "null" || string.IsNullOrEmpty(json)) return "";
|
||||
try
|
||||
{
|
||||
var el = JsonSerializer.Deserialize<JsonElement>(json);
|
||||
return el.ValueKind == JsonValueKind.String
|
||||
? el.GetString() ?? ""
|
||||
: el.GetRawText();
|
||||
}
|
||||
catch { return json; }
|
||||
}
|
||||
|
||||
private static string Js(string code) => JsonSerializer.Serialize(code);
|
||||
|
||||
// ===================== 命令 =====================
|
||||
|
||||
public void Open(string url)
|
||||
{
|
||||
Invoke(async () =>
|
||||
{
|
||||
var nav = new TaskCompletionSource<bool>();
|
||||
void Handler(object? s, CoreWebView2NavigationCompletedEventArgs e)
|
||||
{
|
||||
_web.NavigationCompleted -= Handler;
|
||||
nav.TrySetResult(e.IsSuccess);
|
||||
}
|
||||
_web.NavigationCompleted += Handler;
|
||||
_web.CoreWebView2.Navigate(url);
|
||||
bool ok = await nav.Task;
|
||||
return ok;
|
||||
});
|
||||
}
|
||||
|
||||
public string ExecJs(string js)
|
||||
=> DecodeJsResult(Invoke(() => _web.CoreWebView2.ExecuteScriptAsync(js)));
|
||||
|
||||
public bool Click(string selector)
|
||||
{
|
||||
string js = $$"""
|
||||
(()=>{
|
||||
const el=document.querySelector({{Js(selector)}});
|
||||
if(!el) return false;
|
||||
el.scrollIntoView({block:'center'});
|
||||
el.dispatchEvent(new MouseEvent('mousedown',{bubbles:true}));
|
||||
el.dispatchEvent(new MouseEvent('mouseup',{bubbles:true}));
|
||||
el.click();
|
||||
return true;
|
||||
})()
|
||||
""";
|
||||
return ExecJs(js) == "True";
|
||||
}
|
||||
|
||||
public bool Type(string selector, string text)
|
||||
{
|
||||
string js = $$"""
|
||||
(()=>{
|
||||
const el=document.querySelector({{Js(selector)}});
|
||||
if(!el) return false;
|
||||
const proto = el.tagName==='TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
||||
const setter = Object.getOwnPropertyDescriptor(proto,'value').set;
|
||||
setter.call(el, {{Js(text)}});
|
||||
el.dispatchEvent(new Event('input',{bubbles:true}));
|
||||
el.dispatchEvent(new Event('change',{bubbles:true}));
|
||||
return true;
|
||||
})()
|
||||
""";
|
||||
return ExecJs(js) == "True";
|
||||
}
|
||||
|
||||
private bool Exists(string selector)
|
||||
=> ExecJs($"!!document.querySelector({Js(selector)})") == "True";
|
||||
|
||||
public bool WaitFor(string selector, int timeoutMs)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
while (sw.ElapsedMilliseconds < timeoutMs)
|
||||
{
|
||||
try { if (Exists(selector)) { tcs.TrySetResult(true); return; } }
|
||||
catch { }
|
||||
await Task.Delay(200);
|
||||
}
|
||||
tcs.TrySetResult(false);
|
||||
});
|
||||
return tcs.Task.Wait(timeoutMs + 5000) && tcs.Task.Result;
|
||||
}
|
||||
|
||||
public string GetText(string selector)
|
||||
{
|
||||
string js = $$"""
|
||||
(()=>{
|
||||
const el=document.querySelector({{Js(selector)}});
|
||||
if(!el) return null;
|
||||
return el.value!==undefined ? el.value : (el.innerText||el.textContent);
|
||||
})()
|
||||
""";
|
||||
return ExecJs(js);
|
||||
}
|
||||
|
||||
public void Shot(string path)
|
||||
{
|
||||
InvokeAction(async () =>
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))!);
|
||||
await using var fs = new FileStream(path, FileMode.Create);
|
||||
await _web.CoreWebView2.CapturePreviewAsync(
|
||||
CoreWebView2CapturePreviewImageFormat.Png, fs);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user