Simplify repeated code with OnResult method, fix race condition with multiple calls to On/Off for the same event

This commit is contained in:
rafael-aero
2021-08-18 10:24:12 +02:00
parent 8063f49e4b
commit cbe0637e84
6 changed files with 231 additions and 550 deletions

View File

@@ -1,4 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using SocketIOClient;
using SocketIOClient.Newtonsoft.Json;
@@ -7,7 +10,77 @@ namespace ElectronNET.API
{
internal static class BridgeConnector
{
internal static class EventTasks<T>
{
//Although SocketIO already manage event handlers, we need to manage this here as well for the OnResult calls,
//because SocketIO will simply replace the existing event handler on every call to On(key, ...) , which means there is
//a race condition between On / Off calls that can lead to tasks deadlocking forever without ever triggering their On handler
private static readonly Dictionary<string, TaskCompletionSource<T>> _taskCompletionSources = new();
private static readonly Dictionary<string, string> _eventKeys = new();
private static readonly object _lock = new();
/// <summary>
/// Get or add a new TaskCompletionSource<typeparamref name="T"/> for a given event key
/// </summary>
/// <param name="key"></param>
/// <param name="eventKey"></param>
/// <param name="taskCompletionSource"></param>
/// <param name="waitThisFirstAndThenTryAgain"></param>
/// <returns>Returns true if a new TaskCompletionSource<typeparamref name="T"/> was added to the dictionary</returns>
internal static bool TryGetOrAdd(string key, string eventKey, out TaskCompletionSource<T> taskCompletionSource, out Task waitThisFirstAndThenTryAgain)
{
lock (_lock)
{
if (!_taskCompletionSources.TryGetValue(key, out taskCompletionSource))
{
taskCompletionSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
_taskCompletionSources[key] = taskCompletionSource;
_eventKeys[key] = eventKey;
waitThisFirstAndThenTryAgain = null;
return true; //Was added, so we need to also register the socket events
}
if(_eventKeys.TryGetValue(key, out var existingEventKey) && existingEventKey == eventKey)
{
waitThisFirstAndThenTryAgain = null;
return false; //No need to register the socket events twice
}
waitThisFirstAndThenTryAgain = taskCompletionSource.Task; //Will need to try again after the previous existing one is done
taskCompletionSource = null;
return true; //Need to register the socket events, but must first await the previous task to complete
}
}
/// <summary>
/// Clean up the TaskCompletionSource<typeparamref name="T"/> from the dictionary if and only if it is the same as the passed argument
/// </summary>
/// <param name="key"></param>
/// <param name="eventKey"></param>
/// <param name="taskCompletionSource"></param>
internal static void DoneWith(string key, string eventKey, TaskCompletionSource<T> taskCompletionSource)
{
lock (_lock)
{
if (_taskCompletionSources.TryGetValue(key, out var existingTaskCompletionSource)
&& ReferenceEquals(existingTaskCompletionSource, taskCompletionSource))
{
_taskCompletionSources.Remove(key);
}
if (_eventKeys.TryGetValue(key, out var existingEventKey) && existingEventKey == eventKey)
{
_eventKeys.Remove(key);
}
}
}
}
private static SocketIO _socket;
private static object _syncRoot = new object();
public static void Emit(string eventString, params object[] args)
@@ -21,6 +94,7 @@ namespace ElectronNET.API
{
Console.WriteLine($"Sending event {eventString}");
}
await Socket.EmitAsync(eventString, args);
if (App.SocketDebug)
@@ -30,15 +104,100 @@ namespace ElectronNET.API
});
}
public static void Off(string eventString) => Socket.Off(eventString);
public static void On(string eventString, Action fn) => Socket.On(eventString, _ => fn());
public static void On<T>(string eventString, Action<T> fn) => Socket.On(eventString, (o) => fn(o.GetValue<T>(0)));
public static void Once<T>(string eventString, Action<T> fn) => Socket.On(eventString, (o) =>
public static void Off(string eventString)
{
Socket.Off(eventString);
fn(o.GetValue<T>(0));
});
}
public static void On(string eventString, Action fn)
{
Socket.On(eventString, _ => fn());
}
public static void On<T>(string eventString, Action<T> fn)
{
Socket.On(eventString, (o) => fn(o.GetValue<T>(0)));
}
public static void Once<T>(string eventString, Action<T> fn)
{
On<T>(eventString, (o) =>
{
Off(eventString);
fn(o);
});
}
public static async Task<T> OnResult<T>(string triggerEvent, string completedEvent, params object[] args)
{
string eventKey = triggerEvent;
if (args is object && args.Length > 0) // If there are arguments passed, we generate a unique event key with the arguments
// this allow us to wait for previous events first before registering new ones
{
var hash = new HashCode();
foreach(var obj in args)
{
hash.Add(obj);
}
eventKey = $"{eventKey}-{(uint)hash.ToHashCode()}";
}
if (EventTasks<T>.TryGetOrAdd(triggerEvent, eventKey, out var taskCompletionSource, out var waitThisFirstAndThenTryAgain))
{
if (waitThisFirstAndThenTryAgain is object)
{
//There was a pending call with different parameters, so we need to wait that first and then call here again
try
{
await waitThisFirstAndThenTryAgain;
}
catch
{
//Ignore any exceptions here so we can set a new event below
//The exception will also be visible to the original first caller due to taskCompletionSource.Task
}
//Try again to set the event
return await OnResult<T>(triggerEvent, completedEvent, args);
}
else
{
//A new TaskCompletionSource was added, so we need to register the completed event here
On<T>(completedEvent, (result) =>
{
Off(completedEvent);
taskCompletionSource.SetResult(result);
EventTasks<T>.DoneWith(triggerEvent, eventKey, taskCompletionSource);
});
Emit(triggerEvent, args);
}
}
return await taskCompletionSource.Task;
}
public static async Task<T> OnResult<T>(string triggerEvent, string completedEvent, CancellationToken cancellationToken, params object[] args)
{
var taskCompletionSource = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
On<T>(completedEvent, (result) =>
{
Off(completedEvent);
taskCompletionSource.SetResult(result);
});
Emit(triggerEvent, args);
return await taskCompletionSource.Task.ConfigureAwait(false);
}
}
private static SocketIO Socket
{
get

View File

@@ -951,17 +951,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsFocusedAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isFocused-completed", (isFocused) => {
BridgeConnector.Off("browserWindow-isFocused-completed");
taskCompletionSource.SetResult(isFocused);
});
BridgeConnector.Emit("browserWindowIsFocused", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsFocused", "browserWindow-isFocused-completed", Id);
}
/// <summary>
@@ -970,17 +960,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsDestroyedAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isDestroyed-completed", (isDestroyed) => {
BridgeConnector.Off("browserWindow-isDestroyed-completed");
taskCompletionSource.SetResult(isDestroyed);
});
BridgeConnector.Emit("browserWindowIsDestroyed", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsDestroyed", "browserWindow-isDestroyed-completed", Id);
}
/// <summary>
@@ -1013,17 +993,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsVisibleAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isVisible-completed", (isVisible) => {
BridgeConnector.Off("browserWindow-isVisible-completed");
taskCompletionSource.SetResult(isVisible);
});
BridgeConnector.Emit("browserWindowIsVisible", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsVisible", "browserWindow-isVisible-completed", Id);
}
/// <summary>
@@ -1032,17 +1002,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsModalAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isModal-completed", (isModal) => {
BridgeConnector.Off("browserWindow-isModal-completed");
taskCompletionSource.SetResult(isModal);
});
BridgeConnector.Emit("browserWindowIsModal", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsModal", "browserWindow-isModal-completed", Id);
}
/// <summary>
@@ -1067,17 +1027,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsMaximizedAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isMaximized-completed", (isMaximized) => {
BridgeConnector.Off("browserWindow-isMaximized-completed");
taskCompletionSource.SetResult(isMaximized);
});
BridgeConnector.Emit("browserWindowIsMaximized", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsMaximized", "browserWindow-isMaximized-completed", Id);
}
/// <summary>
@@ -1102,17 +1052,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsMinimizedAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isMinimized-completed", (isMinimized) => {
BridgeConnector.Off("browserWindow-isMinimized-completed");
taskCompletionSource.SetResult(isMinimized);
});
BridgeConnector.Emit("browserWindowIsMinimized", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsMinimized", "browserWindow-isMinimized-completed", Id);
}
/// <summary>
@@ -1129,17 +1069,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsFullScreenAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isFullScreen-completed", (isFullScreen) => {
BridgeConnector.Off("browserWindow-isFullScreen-completed");
taskCompletionSource.SetResult(isFullScreen);
});
BridgeConnector.Emit("browserWindowIsFullScreen", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsFullScreen", "browserWindow-isFullScreen-completed", Id);
}
/// <summary>
@@ -1218,17 +1148,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<Rectangle> GetBoundsAsync()
{
var taskCompletionSource = new TaskCompletionSource<Rectangle>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<Rectangle>("browserWindow-getBounds-completed", (getBounds) => {
BridgeConnector.Off("browserWindow-getBounds-completed");
taskCompletionSource.SetResult(getBounds);
});
BridgeConnector.Emit("browserWindowGetBounds", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<Rectangle>("browserWindowGetBounds", "browserWindow-getBounds-completed", Id);
}
/// <summary>
@@ -1256,17 +1176,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<Rectangle> GetContentBoundsAsync()
{
var taskCompletionSource = new TaskCompletionSource<Rectangle>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<Rectangle>("browserWindow-getContentBounds-completed", (getContentBounds) => {
BridgeConnector.Off("browserWindow-getContentBounds-completed");
taskCompletionSource.SetResult(getContentBounds);
});
BridgeConnector.Emit("browserWindowGetContentBounds", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<Rectangle>("browserWindowGetContentBounds", "browserWindow-getContentBounds-completed", Id);
}
/// <summary>
@@ -1296,17 +1206,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<int[]> GetSizeAsync()
{
var taskCompletionSource = new TaskCompletionSource<int[]>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<int[]>("browserWindow-getSize-completed", (size) => {
BridgeConnector.Off("browserWindow-getSize-completed");
taskCompletionSource.SetResult(size);
});
BridgeConnector.Emit("browserWindowGetSize", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<int[]>("browserWindowGetSize", "browserWindow-getSize-completed", Id);
}
/// <summary>
@@ -1336,17 +1236,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<int[]> GetContentSizeAsync()
{
var taskCompletionSource = new TaskCompletionSource<int[]>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<int[]>("browserWindow-getContentSize-completed", (size) => {
BridgeConnector.Off("browserWindow-getContentSize-completed");
taskCompletionSource.SetResult(size);
});
BridgeConnector.Emit("browserWindowGetContentSize", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<int[]>("browserWindowGetContentSize", "browserWindow-getContentSize-completed", Id);
}
/// <summary>
@@ -1365,17 +1255,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<int[]> GetMinimumSizeAsync()
{
var taskCompletionSource = new TaskCompletionSource<int[]>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<int[]>("browserWindow-getMinimumSize-completed", (size) => {
BridgeConnector.Off("browserWindow-getMinimumSize-completed");
taskCompletionSource.SetResult(size);
});
BridgeConnector.Emit("browserWindowGetMinimumSize", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<int[]>("browserWindowGetMinimumSize", "browserWindow-getMinimumSize-completed", Id);
}
/// <summary>
@@ -1394,17 +1274,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<int[]> GetMaximumSizeAsync()
{
var taskCompletionSource = new TaskCompletionSource<int[]>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<int[]>("browserWindow-getMaximumSize-completed", (size) => {
BridgeConnector.Off("browserWindow-getMaximumSize-completed");
taskCompletionSource.SetResult(size);
});
BridgeConnector.Emit("browserWindowGetMaximumSize", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<int[]>("browserWindowGetMaximumSize", "browserWindow-getMaximumSize-completed", Id);
}
/// <summary>
@@ -1422,17 +1292,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsResizableAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isResizable-completed", (resizable) => {
BridgeConnector.Off("browserWindow-isResizable-completed");
taskCompletionSource.SetResult(resizable);
});
BridgeConnector.Emit("browserWindowIsResizable", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsResizable", "browserWindow-isResizable-completed", Id);
}
/// <summary>
@@ -1452,17 +1312,7 @@ namespace ElectronNET.API
/// <returns>On Linux always returns true.</returns>
public Task<bool> IsMovableAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isMovable-completed", (movable) => {
BridgeConnector.Off("browserWindow-isMovable-completed");
taskCompletionSource.SetResult(movable);
});
BridgeConnector.Emit("browserWindowIsMovable", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsMovable", "browserWindow-isMovable-completed", Id);
}
/// <summary>
@@ -1482,17 +1332,7 @@ namespace ElectronNET.API
/// <returns>On Linux always returns true.</returns>
public Task<bool> IsMinimizableAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isMinimizable-completed", (minimizable) => {
BridgeConnector.Off("browserWindow-isMinimizable-completed");
taskCompletionSource.SetResult(minimizable);
});
BridgeConnector.Emit("browserWindowIsMinimizable", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsMinimizable", "browserWindow-isMinimizable-completed", Id);
}
/// <summary>
@@ -1512,17 +1352,7 @@ namespace ElectronNET.API
/// <returns>On Linux always returns true.</returns>
public Task<bool> IsMaximizableAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isMaximizable-completed", (maximizable) => {
BridgeConnector.Off("browserWindow-isMaximizable-completed");
taskCompletionSource.SetResult(maximizable);
});
BridgeConnector.Emit("browserWindowIsMaximizable", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsMaximizable", "browserWindow-isMaximizable-completed", Id);
}
/// <summary>
@@ -1540,17 +1370,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsFullScreenableAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isFullScreenable-completed", (fullscreenable) => {
BridgeConnector.Off("browserWindow-isFullScreenable-completed");
taskCompletionSource.SetResult(fullscreenable);
});
BridgeConnector.Emit("browserWindowIsFullScreenable", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsFullScreenable", "browserWindow-isFullScreenable-completed", Id);
}
/// <summary>
@@ -1570,17 +1390,7 @@ namespace ElectronNET.API
/// <returns>On Linux always returns true.</returns>
public Task<bool> IsClosableAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isClosable-completed", (closable) => {
BridgeConnector.Off("browserWindow-isClosable-completed");
taskCompletionSource.SetResult(closable);
});
BridgeConnector.Emit("browserWindowIsClosable", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsClosable", "browserWindow-isClosable-completed", Id);
}
/// <summary>
@@ -1630,17 +1440,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsAlwaysOnTopAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isAlwaysOnTop-completed", (isAlwaysOnTop) => {
BridgeConnector.Off("browserWindow-isAlwaysOnTop-completed");
taskCompletionSource.SetResult(isAlwaysOnTop);
});
BridgeConnector.Emit("browserWindowIsAlwaysOnTop", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsAlwaysOnTop", "browserWindow-isAlwaysOnTop-completed", Id);
}
/// <summary>
@@ -1697,17 +1497,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<int[]> GetPositionAsync()
{
var taskCompletionSource = new TaskCompletionSource<int[]>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<int[]>("browserWindow-getPosition-completed", (position) => {
BridgeConnector.Off("browserWindow-getPosition-completed");
taskCompletionSource.SetResult(position);
});
BridgeConnector.Emit("browserWindowGetPosition", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<int[]>("browserWindowGetPosition", "browserWindow-getPosition-completed", Id);
}
/// <summary>
@@ -1727,17 +1517,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<string> GetTitleAsync()
{
var taskCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<string>("browserWindow-getTitle-completed", (title) => {
BridgeConnector.Off("browserWindow-getTitle-completed");
taskCompletionSource.SetResult(title);
});
BridgeConnector.Emit("browserWindowGetTitle", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<string>("browserWindowGetTitle", "browserWindow-getTitle-completed", Id);
}
/// <summary>
@@ -1796,17 +1576,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsKioskAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isKiosk-completed", (isKiosk) => {
BridgeConnector.Off("browserWindow-isKiosk-completed");
taskCompletionSource.SetResult(isKiosk);
});
BridgeConnector.Emit("browserWindowIsKiosk", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsKiosk", "browserWindow-isKiosk-completed", Id);
}
/// <summary>
@@ -1815,17 +1585,7 @@ namespace ElectronNET.API
/// <returns>string of the native handle obtained, HWND on Windows, NSView* on macOS, and Window (unsigned long) on Linux.</returns>
public Task<string> GetNativeWindowHandle()
{
var taskCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<string>("browserWindow-getNativeWindowHandle-completed", (nativeWindowHandle) =>
{
BridgeConnector.Off("browserWindow-getNativeWindowHandle-completed");
taskCompletionSource.SetResult(nativeWindowHandle);
});
BridgeConnector.Emit("browserWindowGetNativeWindowHandle", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<string>("browserWindowGetNativeWindowHandle", "browserWindow-getNativeWindowHandle-completed", Id);
}
/// <summary>
@@ -1844,17 +1604,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<string> GetRepresentedFilenameAsync()
{
var taskCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<string>("browserWindow-getRepresentedFilename-completed", (pathname) => {
BridgeConnector.Off("browserWindow-getRepresentedFilename-completed");
taskCompletionSource.SetResult(pathname);
});
BridgeConnector.Emit("browserWindowGetRepresentedFilename", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<string>("browserWindowGetRepresentedFilename", "browserWindow-getRepresentedFilename-completed", Id);
}
/// <summary>
@@ -1873,17 +1623,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsDocumentEditedAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isDocumentEdited-completed", (edited) => {
BridgeConnector.Off("browserWindow-isDocumentEdited-completed");
taskCompletionSource.SetResult(edited);
});
BridgeConnector.Emit("browserWindowIsDocumentEdited", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsDocumentEdited", "browserWindow-isDocumentEdited-completed", Id);
}
/// <summary>
@@ -1952,7 +1692,8 @@ namespace ElectronNET.API
_items.AddRange(menuItems);
BridgeConnector.Off("windowMenuItemClicked");
BridgeConnector.On<string>("windowMenuItemClicked", (id) => {
BridgeConnector.On<string>("windowMenuItemClicked", (id) =>
{
MenuItem menuItem = _items.GetMenuItem(id);
menuItem?.Click();
});
@@ -2016,17 +1757,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> HasShadowAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-hasShadow-completed", (hasShadow) => {
BridgeConnector.Off("browserWindow-hasShadow-completed");
taskCompletionSource.SetResult(hasShadow);
});
BridgeConnector.Emit("browserWindowHasShadow", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowHasShadow", "browserWindow-hasShadow-completed", Id);
}
/// <summary>
@@ -2036,6 +1767,7 @@ namespace ElectronNET.API
/// The thumbar buttons.
/// </value>
public IReadOnlyCollection<ThumbarButton> ThumbarButtons { get { return _thumbarButtons.AsReadOnly(); } }
private List<ThumbarButton> _thumbarButtons = new List<ThumbarButton>();
/// <summary>
@@ -2054,7 +1786,8 @@ namespace ElectronNET.API
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindowSetThumbarButtons-completed", (success) => {
BridgeConnector.On<bool>("browserWindowSetThumbarButtons-completed", (success) =>
{
BridgeConnector.Off("browserWindowSetThumbarButtons-completed");
taskCompletionSource.SetResult(success);
@@ -2066,7 +1799,8 @@ namespace ElectronNET.API
_thumbarButtons.AddRange(thumbarButtons);
BridgeConnector.Off("thumbarButtonClicked");
BridgeConnector.On<string>("thumbarButtonClicked", (id) => {
BridgeConnector.On<string>("thumbarButtonClicked", (id) =>
{
ThumbarButton thumbarButton = _thumbarButtons.GetThumbarButton(id);
thumbarButton?.Click();
});
@@ -2132,17 +1866,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsMenuBarAutoHideAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isMenuBarAutoHide-completed", (isMenuBarAutoHide) => {
BridgeConnector.Off("browserWindow-isMenuBarAutoHide-completed");
taskCompletionSource.SetResult(isMenuBarAutoHide);
});
BridgeConnector.Emit("browserWindowIsMenuBarAutoHide", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsMenuBarAutoHide", "browserWindow-isMenuBarAutoHide-completed", Id);
}
/// <summary>
@@ -2161,17 +1885,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsMenuBarVisibleAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isMenuBarVisible-completed", (isMenuBarVisible) => {
BridgeConnector.Off("browserWindow-isMenuBarVisible-completed");
taskCompletionSource.SetResult(isMenuBarVisible);
});
BridgeConnector.Emit("browserWindowIsMenuBarVisible", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsMenuBarVisible", "browserWindow-isMenuBarVisible-completed", Id);
}
/// <summary>
@@ -2193,17 +1907,7 @@ namespace ElectronNET.API
/// <returns></returns>
public Task<bool> IsVisibleOnAllWorkspacesAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("browserWindow-isVisibleOnAllWorkspaces-completed", (isVisibleOnAllWorkspaces) => {
BridgeConnector.Off("browserWindow-isVisibleOnAllWorkspaces-completed");
taskCompletionSource.SetResult(isVisibleOnAllWorkspaces);
});
BridgeConnector.Emit("browserWindowIsVisibleOnAllWorkspaces", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("browserWindowIsVisibleOnAllWorkspaces", "browserWindow-isVisibleOnAllWorkspaces-completed", Id);
}
/// <summary>
@@ -2253,44 +1957,20 @@ namespace ElectronNET.API
/// The parent window.
/// </summary>
/// <returns></returns>
public Task<BrowserWindow> GetParentWindowAsync()
public async Task<BrowserWindow> GetParentWindowAsync()
{
var taskCompletionSource = new TaskCompletionSource<BrowserWindow>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<int>("browserWindow-getParentWindow-completed", (id) => {
BridgeConnector.Off("browserWindow-getParentWindow-completed");
var browserWindow = Electron.WindowManager.BrowserWindows.ToList().Single(x => x.Id == id);
taskCompletionSource.SetResult(browserWindow);
});
BridgeConnector.Emit("browserWindowGetParentWindow", Id);
return taskCompletionSource.Task;
var windowID = await BridgeConnector.OnResult<int>("browserWindowGetParentWindow", "browserWindow-getParentWindow-completed", Id);
return Electron.WindowManager.BrowserWindows.Where(w => w.Id == windowID).Single();
}
/// <summary>
/// All child windows.
/// </summary>
/// <returns></returns>
public Task<List<BrowserWindow>> GetChildWindowsAsync()
public async Task<List<BrowserWindow>> GetChildWindowsAsync()
{
var taskCompletionSource = new TaskCompletionSource<List<BrowserWindow>>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<int[]>("browserWindow-getChildWindows-completed", (ids) => {
BridgeConnector.Off("browserWindow-getChildWindows-completed");
var browserWindows = new List<BrowserWindow>();
ids.ToList().ForEach(id =>
{
var browserWindow = Electron.WindowManager.BrowserWindows.ToList().Single(x => x.Id == id);
browserWindows.Add(browserWindow);
});
taskCompletionSource.SetResult(browserWindows);
});
BridgeConnector.Emit("browserWindowGetChildWindows", Id);
return taskCompletionSource.Task;
var windowIDs = new HashSet<int>(await BridgeConnector.OnResult<int[]>("browserWindowGetChildWindows", "browserWindow-getChildWindows-completed", Id));
return Electron.WindowManager.BrowserWindows.Where(w => windowIDs.Contains(w.Id)).ToList();
}
/// <summary>
@@ -2330,7 +2010,7 @@ namespace ElectronNET.API
BridgeConnector.Emit("browserWindow-setBrowserView", Id, browserView.Id);
}
private JsonSerializer _jsonSerializer = new JsonSerializer()
private static readonly JsonSerializer _jsonSerializer = new JsonSerializer()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore

View File

@@ -50,24 +50,11 @@ namespace ElectronNET.API
/// <param name="type">Can be critical or informational. The default is informational.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Return an ID representing the request.</returns>
public async Task<int> BounceAsync(DockBounceType type, CancellationToken cancellationToken = default)
public Task<int> BounceAsync(DockBounceType type, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.On<int>("dock-bounce-completed", (id) =>
{
BridgeConnector.Off("dock-bounce-completed");
taskCompletionSource.SetResult(id);
});
BridgeConnector.Emit("dock-bounce", type.GetDescription());
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
return BridgeConnector.OnResult<int>("dock-bounce", "dock-bounce-completed", cancellationToken, type.GetDescription());
}
/// <summary>
@@ -102,24 +89,10 @@ namespace ElectronNET.API
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The badge string of the dock.</returns>
public async Task<string> GetBadgeAsync(CancellationToken cancellationToken = default)
public Task<string> GetBadgeAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.On<string>("dock-getBadge-completed", (text) =>
{
BridgeConnector.Off("dock-getBadge-completed");
taskCompletionSource.SetResult(text);
});
BridgeConnector.Emit("dock-getBadge");
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
return BridgeConnector.OnResult<string>("dock-getBadge", "dock-getBadge-completed", cancellationToken);
}
/// <summary>
@@ -144,24 +117,10 @@ namespace ElectronNET.API
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the dock icon is visible.</returns>
public async Task<bool> IsVisibleAsync(CancellationToken cancellationToken = default)
public Task<bool> IsVisibleAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
BridgeConnector.On<bool>("dock-isVisible-completed", (isVisible) =>
{
BridgeConnector.Off("dock-isVisible-completed");
taskCompletionSource.SetResult(isVisible);
});
BridgeConnector.Emit("dock-isVisible");
return await taskCompletionSource.Task
.ConfigureAwait(false);
}
return BridgeConnector.OnResult<bool>("dock-isVisible", "dock-isVisible-completed", cancellationToken);
}
/// <summary>
@@ -223,7 +182,7 @@ namespace ElectronNET.API
BridgeConnector.Emit("dock-setIcon", image);
}
private JsonSerializer _jsonSerializer = new JsonSerializer()
private static readonly JsonSerializer _jsonSerializer = new JsonSerializer()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore

View File

@@ -126,17 +126,7 @@ namespace ElectronNET.API
/// </summary>
public Task<bool> ShouldUseDarkColorsAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("nativeTheme-shouldUseDarkColors-completed", (shouldUseDarkColors) => {
BridgeConnector.Off("nativeTheme-shouldUseDarkColors-completed");
taskCompletionSource.SetResult(shouldUseDarkColors);
});
BridgeConnector.Emit("nativeTheme-shouldUseDarkColors");
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("nativeTheme-shouldUseDarkColors", "nativeTheme-shouldUseDarkColors-completed");
}
/// <summary>
@@ -145,17 +135,7 @@ namespace ElectronNET.API
/// </summary>
public Task<bool> ShouldUseHighContrastColorsAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("nativeTheme-shouldUseHighContrastColors-completed", (shouldUseHighContrastColors) => {
BridgeConnector.Off("nativeTheme-shouldUseHighContrastColors-completed");
taskCompletionSource.SetResult(shouldUseHighContrastColors);
});
BridgeConnector.Emit("nativeTheme-shouldUseHighContrastColors");
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("nativeTheme-shouldUseHighContrastColors", "nativeTheme-shouldUseHighContrastColors-completed");
}
/// <summary>
@@ -164,17 +144,7 @@ namespace ElectronNET.API
/// </summary>
public Task<bool> ShouldUseInvertedColorSchemeAsync()
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("nativeTheme-shouldUseInvertedColorScheme-completed", (shouldUseInvertedColorScheme) => {
BridgeConnector.Off("nativeTheme-shouldUseInvertedColorScheme-completed");
taskCompletionSource.SetResult(shouldUseInvertedColorScheme);
});
BridgeConnector.Emit("nativeTheme-shouldUseInvertedColorScheme");
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("nativeTheme-shouldUseInvertedColorScheme", "nativeTheme-shouldUseInvertedColorScheme-completed");
}
/// <summary>

View File

@@ -123,18 +123,7 @@ namespace ElectronNET.API
/// <returns> Whether the item was successfully moved to the trash.</returns>
public Task<bool> TrashItemAsync(string fullPath)
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("shell-trashItem-completed", (success) =>
{
BridgeConnector.Off("shell-trashItem-completed");
taskCompletionSource.SetResult(success);
});
BridgeConnector.Emit("shell-trashItem", fullPath);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("shell-trashItem", "shell-trashItem-completed", fullPath);
}
/// <summary>
@@ -154,18 +143,7 @@ namespace ElectronNET.API
/// <returns>Whether the shortcut was created successfully.</returns>
public Task<bool> WriteShortcutLinkAsync(string shortcutPath, ShortcutLinkOperation operation, ShortcutDetails options)
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("shell-writeShortcutLinkCompleted", (success) =>
{
BridgeConnector.Off("shell-writeShortcutLinkCompleted");
taskCompletionSource.SetResult(success);
});
BridgeConnector.Emit("shell-writeShortcutLink", shortcutPath, operation.GetDescription(), JObject.FromObject(options, _jsonSerializer));
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("shell-writeShortcutLink", "shell-writeShortcutLinkCompleted", shortcutPath, operation.GetDescription(), JObject.FromObject(options, _jsonSerializer));
}
/// <summary>
@@ -176,17 +154,7 @@ namespace ElectronNET.API
/// <returns><see cref="ShortcutDetails"/> of the shortcut.</returns>
public Task<ShortcutDetails> ReadShortcutLinkAsync(string shortcutPath)
{
var taskCompletionSource = new TaskCompletionSource<ShortcutDetails>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<ShortcutDetails>("shell-readShortcutLinkCompleted", (shortcutDetails) =>
{
BridgeConnector.Off("shell-readShortcutLinkCompleted");
taskCompletionSource.SetResult(shortcutDetails);
});
BridgeConnector.Emit("shell-readShortcutLink", shortcutPath);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<ShortcutDetails>("shell-readShortcutLink", "shell-readShortcutLinkCompleted", shortcutPath);
}
private readonly JsonSerializer _jsonSerializer = new JsonSerializer()

View File

@@ -113,18 +113,7 @@ namespace ElectronNET.API
/// <returns>printers</returns>
public Task<PrinterInfo[]> GetPrintersAsync()
{
var taskCompletionSource = new TaskCompletionSource<PrinterInfo[]>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<PrinterInfo[]>("webContents-getPrinters-completed", (printers) =>
{
BridgeConnector.Off("webContents-getPrinters-completed");
taskCompletionSource.SetResult(printers);
});
BridgeConnector.Emit("webContents-getPrinters", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<PrinterInfo[]>("webContents-getPrinters", "webContents-getPrinters-completed", Id);
}
/// <summary>
@@ -134,24 +123,7 @@ namespace ElectronNET.API
/// <returns>success</returns>
public Task<bool> PrintAsync(PrintOptions options = null)
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("webContents-print-completed", (success) =>
{
BridgeConnector.Off("webContents-print-completed");
taskCompletionSource.SetResult(success);
});
if(options == null)
{
BridgeConnector.Emit("webContents-print", Id, "");
}
else
{
BridgeConnector.Emit("webContents-print", Id, JObject.FromObject(options, _jsonSerializer));
}
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("webContents-print", "webContents-print-completed", Id, options is null ? (object)"" : JObject.FromObject(options, _jsonSerializer));
}
/// <summary>
@@ -165,24 +137,7 @@ namespace ElectronNET.API
/// <returns>success</returns>
public Task<bool> PrintToPDFAsync(string path, PrintToPDFOptions options = null)
{
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On<bool>("webContents-printToPDF-completed", (success) =>
{
BridgeConnector.Off("webContents-printToPDF-completed");
taskCompletionSource.SetResult(success);
});
if(options == null)
{
BridgeConnector.Emit("webContents-printToPDF", Id, "", path);
}
else
{
BridgeConnector.Emit("webContents-printToPDF", Id, JObject.FromObject(options, _jsonSerializer), path);
}
return taskCompletionSource.Task;
return BridgeConnector.OnResult<bool>("webContents-printToPDF", "webContents-printToPDF-completed", Id, options is null ? (object)"" : JObject.FromObject(options, _jsonSerializer), path);
}
/// <summary>
@@ -192,18 +147,7 @@ namespace ElectronNET.API
/// <returns>URL of the loaded page</returns>
public Task<string> GetUrl()
{
var taskCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
var eventString = "webContents-getUrl" + Id;
BridgeConnector.On<string>(eventString, (url) =>
{
BridgeConnector.Off(eventString);
taskCompletionSource.SetResult(url);
});
BridgeConnector.Emit("webContents-getUrl", Id);
return taskCompletionSource.Task;
return BridgeConnector.OnResult<string>("webContents-getUrl", "webContents-getUrl" + Id, Id);
}
/// <summary>
@@ -237,19 +181,20 @@ namespace ElectronNET.API
/// <param name="url"></param>
/// <param name="options"></param>
public Task LoadURLAsync(string url, LoadURLOptions options)
{
var taskCompletionSource = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
{
var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On("webContents-loadURL-complete" + Id, () =>
{
BridgeConnector.Off("webContents-loadURL-complete" + Id);
BridgeConnector.Off("webContents-loadURL-error" + Id);
taskCompletionSource.SetResult(null);
taskCompletionSource.SetResult();
});
BridgeConnector.On<string>("webContents-loadURL-error" + Id, (error) =>
{
BridgeConnector.Off("webContents-loadURL-error" + Id);
BridgeConnector.Off("webContents-loadURL-complete" + Id);
taskCompletionSource.SetException(new InvalidOperationException(error.ToString()));
});
@@ -270,7 +215,7 @@ namespace ElectronNET.API
BridgeConnector.Emit("webContents-insertCSS", Id, isBrowserWindow, path);
}
private JsonSerializer _jsonSerializer = new JsonSerializer()
private static readonly JsonSerializer _jsonSerializer = new JsonSerializer()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,