Skip to content

Commit 22c16ed

Browse files
committed
Switch to a new internal function/event registration system
1 parent b0d0ead commit 22c16ed

7 files changed

Lines changed: 467 additions & 101 deletions

File tree

examples/WpfControllerApp/MainWindow.xaml.cs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,32 +38,61 @@ public WinDispatcher(Dispatcher dispatcher)
3838

3939
public void InvokeWithVoid(Action action) => dispatcher.Invoke(action);
4040
}
41-
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
41+
private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
4242
{
4343
WebUI.Window.UseSpecificDispatcher = new WinDispatcher(Dispatcher);
4444

4545
window = new(new WebUI.WindowProperties { Width = 640, Height = 480, X = 800, Y = 50 });
4646
var evts = window.RegisterDefaultEventHandler();
47-
evts.OnDisconnect += (_) =>
47+
48+
//evts.OnDisconnect += (_) =>
49+
//{
50+
// window = null;
51+
// Dispatcher.InvokeAsync(() => Close());
52+
//};
53+
54+
window.InitOurBridge();
55+
//window.Show("<html><head><script src='webui.js'></script></head><body>Hi Bob</body></html>");
56+
//await Task.Delay(2000);
57+
//btnDevTools_Click(null, null);
58+
// await Task.Delay(2000);
59+
60+
evts.OnConnect += (window) =>
4861
{
49-
window = null;
50-
Dispatcher.InvokeAsync(() => Close());
62+
//this.window = window;
63+
NewConnect();
5164
};
65+
66+
67+
5268
window.Show("index.html");
5369

54-
window.RegisterOnClick(JSClickedButton, "TestButton");
55-
window.RegisterBoundFunction(() => MyTest_function);
70+
//window.RegisterBoundFunction(() => MyTest_function);
71+
}
72+
private async void NewConnect()
73+
{
74+
window.SetDebug();
75+
window.AddEventListener(JSClickedButton, CommonEventTypes.click, "TestButton");
76+
window.RegisterBoundFunction("MyTest_function", RawMyTestCalled);
77+
5678
}
79+
80+
private Task<string> RawMyTestCalled(string serialized)
81+
{
82+
var args = Newtonsoft.Json.JsonConvert.DeserializeObject<string[]>(serialized);
83+
return MyTest_function(args[0], args[1], double.Parse(args[2]));
84+
}
85+
5786
private async Task<string> MyTest_function(String arg1, String arg2, double arg3)
5887
{
5988
var str = $"I am called: {arg1}({arg1.GetType()}) and {arg2}({arg2.GetType()}) {arg3}({arg3.GetType()})";
6089
LogItem(str);
6190
await Task.Delay(500);
6291
return "Nice Call " + arg1;
6392
}
64-
private void JSClickedButton(String elem)
93+
private void JSClickedButton(DomEvent evt)
6594
{
66-
LogItem($"Got a click from JS for: {elem}");
95+
LogItem($"Got a click from JS for: {evt.originalTargetId}");
6796
}
6897

6998
private void LogItem(object val, [CallerArgumentExpression(nameof(val))] string expression = "unknown")

examples/WpfControllerApp/index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
44
<head>
55
<meta charset="utf-8" />
6-
<script src="webui.js"></script>
6+
<script src="webui.js"></script>
7+
<script src="webui_net.js"></script>
78
<title>JS Interop</title>
89
<script>
910

@@ -25,7 +26,7 @@
2526
const getComplexObj = () => complexObj;
2627

2728
async function btn2Clicked() {
28-
let res = await webui.call("MyTest_function", "an", "arg", 12345.78);
29+
let res = await MyTest_function("1st JS Arg Passed to C#", "arg", 12345.78);
2930
alert("You returned: " + res);
3031
}
3132

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using Newtonsoft.Json;
3+
4+
namespace WebUI
5+
{
6+
public class MilisecondEpochConverter : RandomNumberTimeConverter<long, DateTime>
7+
{
8+
private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
9+
10+
public MilisecondEpochConverter() : base(long.Parse) { }
11+
12+
public static DateTime ParseLong(long val) => _epoch.AddMilliseconds(val);
13+
14+
protected override DateTime ParseValue(long val) => ParseLong(val);
15+
16+
protected override string WriteValue(DateTime when)
17+
{
18+
return ((long)(when - _epoch).TotalMilliseconds).ToString();
19+
}
20+
public override bool CanConvert(Type objectType)
21+
{
22+
if (objectType == typeof(DateTime) || objectType == typeof(DateTime?))
23+
{
24+
return true;
25+
}
26+
27+
if (objectType == typeof(DateTimeOffset) || objectType == typeof(DateTimeOffset?))
28+
{
29+
return true;
30+
}
31+
32+
return false;
33+
}
34+
}
35+
public abstract class RandomNumberTimeConverter<T, SOURCE_TYPE> : JsonConverter
36+
{
37+
protected RandomNumberTimeConverter(Func<String, T> ParseNotNullStringHandler)
38+
{
39+
this.ParseNotNullStringHandler = ParseNotNullStringHandler;
40+
}
41+
Func<String, T> ParseNotNullStringHandler;
42+
43+
protected abstract SOURCE_TYPE ParseValue(T val);
44+
protected abstract String WriteValue(SOURCE_TYPE when);
45+
46+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
47+
{
48+
writer.WriteRawValue(WriteValue((SOURCE_TYPE)value));
49+
50+
51+
}
52+
53+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
54+
{
55+
if (reader.Value == null) { return null; }
56+
T val;
57+
if (reader.TokenType == JsonToken.String)
58+
val = ParseNotNullStringHandler((string)reader.Value);
59+
else
60+
val = (T)Convert.ChangeType(reader.Value, typeof(T));
61+
return ParseValue(val);
62+
}
63+
}
64+
}

src/WebUI.NET/WebUI.NET.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,10 @@
6464
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
6565
</ItemGroup>
6666

67+
<ItemGroup>
68+
<None Update="webui_net.js">
69+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
70+
</None>
71+
</ItemGroup>
72+
6773
</Project>

src/WebUI.NET/WebUINetUI.cs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Runtime.CompilerServices;
8+
using System.Text;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Newtonsoft.Json;
12+
13+
namespace WebUI
14+
{
15+
public abstract class RandomNumberTimeConverter<T, SOURCE_TYPE> : JsonConverter
16+
{
17+
protected RandomNumberTimeConverter(Func<String, T> ParseNotNullStringHandler)
18+
{
19+
this.ParseNotNullStringHandler = ParseNotNullStringHandler;
20+
}
21+
Func<String, T> ParseNotNullStringHandler;
22+
23+
protected abstract SOURCE_TYPE ParseValue(T val);
24+
protected abstract String WriteValue(SOURCE_TYPE when);
25+
26+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
27+
{
28+
writer.WriteRawValue(WriteValue((SOURCE_TYPE)value));
29+
30+
31+
}
32+
33+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
34+
{
35+
if (reader.Value == null) { return null; }
36+
T val;
37+
if (reader.TokenType == JsonToken.String)
38+
val = ParseNotNullStringHandler((string)reader.Value);
39+
else
40+
val = (T)Convert.ChangeType(reader.Value, typeof(T));
41+
return ParseValue(val);
42+
}
43+
}
44+
public class DomEvent
45+
{
46+
47+
public string currentTargetId { get; set; }
48+
public string originalTargetId { get; set; }
49+
[JsonConverter(typeof(MilisecondEpochConverter))]
50+
public DateTime timestamp { get; set; }
51+
public string type { get; set; }
52+
public Dictionary<string, string> additionalProps; //map of additional property values requested
53+
54+
}
55+
public enum CommonEventTypes { load, unload, error, resize, scroll, focus, blur, click, dblclick, mousedown, mouseup, mouseover, mouseout, mousemove, input, keydown, keypress, keyup, submit, change, DOMNodeInserted, DOMNodeRemoved, DOMSubtreeModified, DOMNodeInsertedIntoDocument, DOMNodeRemovedFromDocument, DOMContentLoaded, hashchange, beforeunload }
56+
public static class WebUINetUI
57+
{
58+
59+
public class EventOpts
60+
{
61+
public bool capture { get; set; }
62+
public bool once { get; set; }
63+
public string abortKey { get; set; }
64+
65+
}
66+
private const int startId = 5432;
67+
private static volatile int curFuncId = startId;
68+
// CommonEventTypes
69+
public static void AddEventListener(this Window window, Action<DomEvent> OnFired, CommonEventTypes eventType, String domId, EventOpts additionalOpts = null, params KeyValuePair<string, string>[] additionalEventCaptureProps) => window.AddEventListener(OnFired, eventType.ToString(), domId, additionalOpts, additionalEventCaptureProps);
70+
71+
public static void AddEventListener(this Window window, Action<DomEvent> OnFired, String eventType, String domId, EventOpts additionalOpts = null, params KeyValuePair<string, string>[] additionalEventCaptureProps)
72+
{
73+
74+
//addCSEventListener(type, elementID, csFuncID, additionalProps, options, abortKey)
75+
var addlDict = additionalEventCaptureProps?.Length > 0 ?
76+
additionalEventCaptureProps.ToDictionary(x => x.Key, x => x.Value) : null;
77+
var abortKey = additionalOpts?.abortKey;
78+
if (abortKey != null)
79+
additionalOpts.abortKey = null;
80+
var curId = window._RegisterBoundFunction(OnCalled: (str) => OnFired(Newtonsoft.Json.JsonConvert.DeserializeObject<DomEvent[]>(str)[0]), registerFunc: false);
81+
window.InvokeJavascriptMethod($"{ourJSClass}.addCSEventListener", eventType, domId, curId, addlDict, additionalOpts, abortKey);
82+
}
83+
private static async void JavascriptFuncCallback(this Window window, int csFuncId, int jsCallId, string jsonOfArgs)
84+
{
85+
if (!csFuncIdToFired.TryGetValue(csFuncId, out var info))
86+
throw new Exception($"The JS func callback handler was passed an invalid function id {csFuncId}"); ;
87+
if (info.normFunc != null)
88+
{
89+
Window.UseSpecificDispatcher.InvokeWithVoid(() => info.normFunc(jsonOfArgs));
90+
return;
91+
}
92+
var res = await Window.UseSpecificDispatcher.InvokeWithReturnType(() => info.taskFunc(jsonOfArgs));
93+
94+
await window.ScriptEvaluateMethod<string>($"{ourJSClass}.setCSFunctionResult", jsCallId, res);//we don't use invoke so we can get the error
95+
//window.InvokeJavascriptMethod("setCSFunctionResult", jsCallId, res);
96+
97+
}
98+
private const string ourJSClass = $"window.WebUINet";
99+
public static void SetDebug(this Window window, bool debugging = true)
100+
{
101+
var jsDebug = debugging ? "true" : "false";
102+
window.InvokeJavaScript($@"{ourJSClass}.DEBUG_MODE={jsDebug};
103+
window.WebuiBridge.setLogging({jsDebug});
104+
");
105+
}
106+
public static void RegisterBoundFunction(this Window window, String RegisteredName, Func<string, Task<string>> OnCalled) => window._RegisterBoundFunction(RegisteredName, OnCalledTask: OnCalled);
107+
public static void RegisterBoundFunction(this Window window, String RegisteredName, Action<string> OnCalled) => window._RegisterBoundFunction(RegisteredName, OnCalled);
108+
109+
private static int _RegisterBoundFunction(this Window window, String RegisteredName = null, Action<string> OnCalled = null, Func<string, Task<string>> OnCalledTask = null, bool registerFunc = true)
110+
{
111+
var curId = Interlocked.Increment(ref curFuncId);
112+
csFuncIdToFired[curId] = new RegisteredFunc { taskFunc = OnCalledTask, normFunc = OnCalled };
113+
if (registerFunc)
114+
{
115+
//we don't use invoke so we can get the error
116+
window.ScriptEvaluateMethod<string>($"{ourJSClass}.addCSFunction", curId, RegisteredName, OnCalledTask != null); //not awaiting but exception will at least show in logs just no ST
117+
//window.InvokeJavascriptMethod("WebUINet.addCSFunction", curId, RegisteredName, OnCalledTask != null);
118+
}
119+
if (curId == startId + 1)
120+
{
121+
window.RegisterEventHandler(RawFuncCallback, "webuiNet_Callback");
122+
}
123+
return curId;
124+
}
125+
public static void InitOurBridge(this Window window)
126+
{
127+
OurJSBytes = Encoding.UTF8.GetBytes(File.ReadAllText("webui_net.js"));
128+
window.SetFileHandler((path) => path == "/webui_net.js" ? OurJSBytes : null);
129+
}
130+
private static byte[] OurJSBytes;
131+
private static object RawFuncCallback(WebUI.Events.Event evt, string element, ulong handlerId)
132+
{
133+
evt.Window.JavascriptFuncCallback((int)evt.GetNumber(0), (int)evt.GetNumber(1), evt.GetString(2));
134+
return null;
135+
}
136+
137+
private class RegisteredFunc
138+
{
139+
public Func<string, Task<string>> taskFunc;
140+
public Action<string> normFunc;
141+
}
142+
private static ConcurrentDictionary<int, RegisteredFunc> csFuncIdToFired = new();
143+
}
144+
}

0 commit comments

Comments
 (0)