-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Expand file tree
/
Copy pathWebView2Extensions.cs
More file actions
149 lines (133 loc) · 5.27 KB
/
WebView2Extensions.cs
File metadata and controls
149 lines (133 loc) · 5.27 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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright (c) Mahmoud Al-Qudsi, NeoSmart Technoogies. All rights reserved.
// Licensed under the MIT License.
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
using System.Reflection;
using System.Text;
using System.Text.Encodings.Web;
using Windows.Foundation;
namespace Files.App.Extensions
{
using WebViewMessageReceivedHandler = TypedEventHandler<WebView2, CoreWebView2WebMessageReceivedEventArgs>;
/// <summary>
/// Code modified from https://gist.github.com/mqudsi/ceb4ecee76eb4c32238a438664783480
/// </summary>
public static class WebView2Extensions
{
public static void Navigate(this WebView2 webview, Uri url)
{
webview.Source = url;
}
private struct WebMessage
{
public Guid Guid { get; set; }
}
private struct MethodWebMessage
{
public long Id { get; set; }
public string Method { get; set; }
public string Args { get; set; }
}
private static readonly JsonSerializerOptions _caseInsensitiveOptions = new() { PropertyNameCaseInsensitive = true };
private static readonly JsonSerializerOptions _unsafeRelaxedOptions = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
public static List<WebViewMessageReceivedHandler> _handlers = new();
public static async Task AddWebAllowedObject<T>(this WebView2 webview, string name, T @object)
{
var sb = new StringBuilder();
sb.AppendLine($"window.{name} = {{ ");
var methodsGuid = Guid.NewGuid();
var methodInfo = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Instance);
var methods = new Dictionary<string, MethodInfo>(methodInfo.Length);
foreach (var method in methodInfo)
{
var functionName = $"{char.ToLower(method.Name[0])}{method.Name.Substring(1)}";
sb.AppendLine($@"{functionName}: function() {{ window.chrome.webview.postMessage(JSON.stringify({{ guid: ""{methodsGuid}"", id: this._callbackIndex++, method: ""{functionName}"", args: JSON.stringify([...arguments]) }})); const promise = new Promise((accept, reject) => this._callbacks.set(this._callbackIndex, {{ accept: accept, reject: reject }})); return promise; }},");
methods.Add(functionName, method);
}
sb.AppendLine($@"_callbacks: new Map(),");
sb.Append($@"_callbackIndex: 0,");
sb.AppendLine("}");
try
{
await webview.ExecuteScriptAsync($"{sb}").AsTask();
}
catch (Exception)
{
}
var handler = (WebViewMessageReceivedHandler)(async (_, e) =>
{
var rawMessage = e.TryGetWebMessageAsString();
var message = JsonSerializer.Deserialize<WebMessage>(rawMessage, _caseInsensitiveOptions);
if (message.Guid != methodsGuid)
return;
var methodMessage = JsonSerializer.Deserialize<MethodWebMessage>(rawMessage, _caseInsensitiveOptions);
var method = methods[methodMessage.Method];
try
{
var args = JsonSerializer.Deserialize<JsonElement[]>(methodMessage.Args).Zip(method.GetParameters(), (val, args) => new { val, args.ParameterType }).Select(item => item.val.Deserialize(item.ParameterType));
var result = method.Invoke(@object, args.ToArray());
if (result is object)
{
var resultType = result.GetType();
dynamic task = null;
if (resultType.Name.StartsWith("TaskToAsyncOperationAdapter")
|| resultType.IsInstanceOfType(typeof(IAsyncInfo)))
{
if (resultType.GenericTypeArguments.Length > 0)
{
var asTask = typeof(WindowsRuntimeSystemExtensions)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(method => method.GetParameters().Length == 1
&& method.Name == "AsTask"
&& method.ToString().Contains("Windows.Foundation.IAsyncOperation`1[TResult]"))
.FirstOrDefault();
asTask = asTask.MakeGenericMethod(resultType.GenericTypeArguments[0]);
task = (Task)asTask.Invoke(null, new[] { result });
}
else
{
task = WindowsRuntimeSystemExtensions.AsTask((dynamic)result);
}
}
else
{
var awaiter = resultType.GetMethod(nameof(Task.GetAwaiter));
if (awaiter is object)
task = result;
}
if (task is object)
result = await task;
}
var json = JsonSerializer.Serialize(result, _unsafeRelaxedOptions);
await webview.ExecuteScriptAsync($@"{name}._callbacks.get({methodMessage.Id}).accept(JSON.parse({json})); {name}._callbacks.delete({methodMessage.Id});");
}
catch (Exception ex)
{
var json = JsonSerializer.Serialize(ex, _unsafeRelaxedOptions);
await webview.ExecuteScriptAsync($@"{name}._callbacks.get({methodMessage.Id}).reject(JSON.parse({json})); {name}._callbacks.delete({methodMessage.Id});");
}
});
_handlers.Add(handler);
webview.WebMessageReceived += handler;
}
public static async Task<string> InvokeScriptAsync(this WebView2 webview, string function, params object[] args)
{
var array = JsonSerializer.Serialize(args, _unsafeRelaxedOptions);
string result = null;
await webview.DispatcherQueue.EnqueueAsync(async () =>
{
var script = $"{function}(...{array});";
try
{
result = await webview.ExecuteScriptAsync(script).AsTask();
result = JsonSerializer.Deserialize<string>(result);
}
catch (Exception)
{
}
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal);
return result;
}
}
}