diff --git a/ElectronNET.API/BridgeConnector.cs b/ElectronNET.API/BridgeConnector.cs index b5ae092..f4ab793 100644 --- a/ElectronNET.API/BridgeConnector.cs +++ b/ElectronNET.API/BridgeConnector.cs @@ -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 + { + //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> _taskCompletionSources = new(); + private static readonly Dictionary _eventKeys = new(); + private static readonly object _lock = new(); + + /// + /// Get or add a new TaskCompletionSource for a given event key + /// + /// + /// + /// + /// + /// Returns true if a new TaskCompletionSource was added to the dictionary + internal static bool TryGetOrAdd(string key, string eventKey, out TaskCompletionSource 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 + } + } + + /// + /// Clean up the TaskCompletionSource from the dictionary if and only if it is the same as the passed argument + /// + /// + /// + /// + internal static void DoneWith(string key, string eventKey, TaskCompletionSource 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(string eventString, Action fn) => Socket.On(eventString, (o) => fn(o.GetValue(0))); - public static void Once(string eventString, Action fn) => Socket.On(eventString, (o) => + public static void Off(string eventString) { Socket.Off(eventString); - fn(o.GetValue(0)); - }); + } + public static void On(string eventString, Action fn) + { + Socket.On(eventString, _ => fn()); + } + + public static void On(string eventString, Action fn) + { + Socket.On(eventString, (o) => fn(o.GetValue(0))); + } + + public static void Once(string eventString, Action fn) + { + On(eventString, (o) => + { + Off(eventString); + fn(o); + }); + } + + public static async Task OnResult(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.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(triggerEvent, completedEvent, args); + } + else + { + //A new TaskCompletionSource was added, so we need to register the completed event here + + On(completedEvent, (result) => + { + Off(completedEvent); + taskCompletionSource.SetResult(result); + EventTasks.DoneWith(triggerEvent, eventKey, taskCompletionSource); + }); + + Emit(triggerEvent, args); + } + } + + return await taskCompletionSource.Task; + } + + + public static async Task OnResult(string triggerEvent, string completedEvent, CancellationToken cancellationToken, params object[] args) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled())) + { + + On(completedEvent, (result) => + { + Off(completedEvent); + taskCompletionSource.SetResult(result); + }); + + Emit(triggerEvent, args); + + return await taskCompletionSource.Task.ConfigureAwait(false); + } + } private static SocketIO Socket { get diff --git a/ElectronNET.API/BrowserWindow.cs b/ElectronNET.API/BrowserWindow.cs index aea3cf6..37fa676 100644 --- a/ElectronNET.API/BrowserWindow.cs +++ b/ElectronNET.API/BrowserWindow.cs @@ -951,17 +951,7 @@ namespace ElectronNET.API /// public Task IsFocusedAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isFocused-completed", (isFocused) => { - BridgeConnector.Off("browserWindow-isFocused-completed"); - - taskCompletionSource.SetResult(isFocused); - }); - - BridgeConnector.Emit("browserWindowIsFocused", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsFocused", "browserWindow-isFocused-completed", Id); } /// @@ -970,17 +960,7 @@ namespace ElectronNET.API /// public Task IsDestroyedAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isDestroyed-completed", (isDestroyed) => { - BridgeConnector.Off("browserWindow-isDestroyed-completed"); - - taskCompletionSource.SetResult(isDestroyed); - }); - - BridgeConnector.Emit("browserWindowIsDestroyed", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsDestroyed", "browserWindow-isDestroyed-completed", Id); } /// @@ -1013,17 +993,7 @@ namespace ElectronNET.API /// public Task IsVisibleAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isVisible-completed", (isVisible) => { - BridgeConnector.Off("browserWindow-isVisible-completed"); - - taskCompletionSource.SetResult(isVisible); - }); - - BridgeConnector.Emit("browserWindowIsVisible", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsVisible", "browserWindow-isVisible-completed", Id); } /// @@ -1032,17 +1002,7 @@ namespace ElectronNET.API /// public Task IsModalAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isModal-completed", (isModal) => { - BridgeConnector.Off("browserWindow-isModal-completed"); - - taskCompletionSource.SetResult(isModal); - }); - - BridgeConnector.Emit("browserWindowIsModal", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsModal", "browserWindow-isModal-completed", Id); } /// @@ -1067,17 +1027,7 @@ namespace ElectronNET.API /// public Task IsMaximizedAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isMaximized-completed", (isMaximized) => { - BridgeConnector.Off("browserWindow-isMaximized-completed"); - - taskCompletionSource.SetResult(isMaximized); - }); - - BridgeConnector.Emit("browserWindowIsMaximized", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsMaximized", "browserWindow-isMaximized-completed", Id); } /// @@ -1102,17 +1052,7 @@ namespace ElectronNET.API /// public Task IsMinimizedAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isMinimized-completed", (isMinimized) => { - BridgeConnector.Off("browserWindow-isMinimized-completed"); - - taskCompletionSource.SetResult(isMinimized); - }); - - BridgeConnector.Emit("browserWindowIsMinimized", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsMinimized", "browserWindow-isMinimized-completed", Id); } /// @@ -1129,17 +1069,7 @@ namespace ElectronNET.API /// public Task IsFullScreenAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isFullScreen-completed", (isFullScreen) => { - BridgeConnector.Off("browserWindow-isFullScreen-completed"); - - taskCompletionSource.SetResult(isFullScreen); - }); - - BridgeConnector.Emit("browserWindowIsFullScreen", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsFullScreen", "browserWindow-isFullScreen-completed", Id); } /// @@ -1218,17 +1148,7 @@ namespace ElectronNET.API /// public Task GetBoundsAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-getBounds-completed", (getBounds) => { - BridgeConnector.Off("browserWindow-getBounds-completed"); - - taskCompletionSource.SetResult(getBounds); - }); - - BridgeConnector.Emit("browserWindowGetBounds", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowGetBounds", "browserWindow-getBounds-completed", Id); } /// @@ -1256,17 +1176,7 @@ namespace ElectronNET.API /// public Task GetContentBoundsAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-getContentBounds-completed", (getContentBounds) => { - BridgeConnector.Off("browserWindow-getContentBounds-completed"); - - taskCompletionSource.SetResult(getContentBounds); - }); - - BridgeConnector.Emit("browserWindowGetContentBounds", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowGetContentBounds", "browserWindow-getContentBounds-completed", Id); } /// @@ -1296,17 +1206,7 @@ namespace ElectronNET.API /// public Task GetSizeAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-getSize-completed", (size) => { - BridgeConnector.Off("browserWindow-getSize-completed"); - - taskCompletionSource.SetResult(size); - }); - - BridgeConnector.Emit("browserWindowGetSize", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowGetSize", "browserWindow-getSize-completed", Id); } /// @@ -1336,17 +1236,7 @@ namespace ElectronNET.API /// public Task GetContentSizeAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-getContentSize-completed", (size) => { - BridgeConnector.Off("browserWindow-getContentSize-completed"); - - taskCompletionSource.SetResult(size); - }); - - BridgeConnector.Emit("browserWindowGetContentSize", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowGetContentSize", "browserWindow-getContentSize-completed", Id); } /// @@ -1365,17 +1255,7 @@ namespace ElectronNET.API /// public Task GetMinimumSizeAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-getMinimumSize-completed", (size) => { - BridgeConnector.Off("browserWindow-getMinimumSize-completed"); - - taskCompletionSource.SetResult(size); - }); - - BridgeConnector.Emit("browserWindowGetMinimumSize", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowGetMinimumSize", "browserWindow-getMinimumSize-completed", Id); } /// @@ -1394,17 +1274,7 @@ namespace ElectronNET.API /// public Task GetMaximumSizeAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-getMaximumSize-completed", (size) => { - BridgeConnector.Off("browserWindow-getMaximumSize-completed"); - - taskCompletionSource.SetResult(size); - }); - - BridgeConnector.Emit("browserWindowGetMaximumSize", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowGetMaximumSize", "browserWindow-getMaximumSize-completed", Id); } /// @@ -1422,17 +1292,7 @@ namespace ElectronNET.API /// public Task IsResizableAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isResizable-completed", (resizable) => { - BridgeConnector.Off("browserWindow-isResizable-completed"); - - taskCompletionSource.SetResult(resizable); - }); - - BridgeConnector.Emit("browserWindowIsResizable", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsResizable", "browserWindow-isResizable-completed", Id); } /// @@ -1452,17 +1312,7 @@ namespace ElectronNET.API /// On Linux always returns true. public Task IsMovableAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isMovable-completed", (movable) => { - BridgeConnector.Off("browserWindow-isMovable-completed"); - - taskCompletionSource.SetResult(movable); - }); - - BridgeConnector.Emit("browserWindowIsMovable", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsMovable", "browserWindow-isMovable-completed", Id); } /// @@ -1482,17 +1332,7 @@ namespace ElectronNET.API /// On Linux always returns true. public Task IsMinimizableAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isMinimizable-completed", (minimizable) => { - BridgeConnector.Off("browserWindow-isMinimizable-completed"); - - taskCompletionSource.SetResult(minimizable); - }); - - BridgeConnector.Emit("browserWindowIsMinimizable", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsMinimizable", "browserWindow-isMinimizable-completed", Id); } /// @@ -1512,17 +1352,7 @@ namespace ElectronNET.API /// On Linux always returns true. public Task IsMaximizableAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isMaximizable-completed", (maximizable) => { - BridgeConnector.Off("browserWindow-isMaximizable-completed"); - - taskCompletionSource.SetResult(maximizable); - }); - - BridgeConnector.Emit("browserWindowIsMaximizable", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsMaximizable", "browserWindow-isMaximizable-completed", Id); } /// @@ -1540,17 +1370,7 @@ namespace ElectronNET.API /// public Task IsFullScreenableAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isFullScreenable-completed", (fullscreenable) => { - BridgeConnector.Off("browserWindow-isFullScreenable-completed"); - - taskCompletionSource.SetResult(fullscreenable); - }); - - BridgeConnector.Emit("browserWindowIsFullScreenable", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsFullScreenable", "browserWindow-isFullScreenable-completed", Id); } /// @@ -1570,17 +1390,7 @@ namespace ElectronNET.API /// On Linux always returns true. public Task IsClosableAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isClosable-completed", (closable) => { - BridgeConnector.Off("browserWindow-isClosable-completed"); - - taskCompletionSource.SetResult(closable); - }); - - BridgeConnector.Emit("browserWindowIsClosable", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsClosable", "browserWindow-isClosable-completed", Id); } /// @@ -1630,17 +1440,7 @@ namespace ElectronNET.API /// public Task IsAlwaysOnTopAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isAlwaysOnTop-completed", (isAlwaysOnTop) => { - BridgeConnector.Off("browserWindow-isAlwaysOnTop-completed"); - - taskCompletionSource.SetResult(isAlwaysOnTop); - }); - - BridgeConnector.Emit("browserWindowIsAlwaysOnTop", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsAlwaysOnTop", "browserWindow-isAlwaysOnTop-completed", Id); } /// @@ -1697,17 +1497,7 @@ namespace ElectronNET.API /// public Task GetPositionAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-getPosition-completed", (position) => { - BridgeConnector.Off("browserWindow-getPosition-completed"); - - taskCompletionSource.SetResult(position); - }); - - BridgeConnector.Emit("browserWindowGetPosition", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowGetPosition", "browserWindow-getPosition-completed", Id); } /// @@ -1727,17 +1517,7 @@ namespace ElectronNET.API /// public Task GetTitleAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-getTitle-completed", (title) => { - BridgeConnector.Off("browserWindow-getTitle-completed"); - - taskCompletionSource.SetResult(title); - }); - - BridgeConnector.Emit("browserWindowGetTitle", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowGetTitle", "browserWindow-getTitle-completed", Id); } /// @@ -1796,17 +1576,7 @@ namespace ElectronNET.API /// public Task IsKioskAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isKiosk-completed", (isKiosk) => { - BridgeConnector.Off("browserWindow-isKiosk-completed"); - - taskCompletionSource.SetResult(isKiosk); - }); - - BridgeConnector.Emit("browserWindowIsKiosk", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsKiosk", "browserWindow-isKiosk-completed", Id); } /// @@ -1815,17 +1585,7 @@ namespace ElectronNET.API /// string of the native handle obtained, HWND on Windows, NSView* on macOS, and Window (unsigned long) on Linux. public Task GetNativeWindowHandle() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-getNativeWindowHandle-completed", (nativeWindowHandle) => - { - BridgeConnector.Off("browserWindow-getNativeWindowHandle-completed"); - taskCompletionSource.SetResult(nativeWindowHandle); - }); - - BridgeConnector.Emit("browserWindowGetNativeWindowHandle", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowGetNativeWindowHandle", "browserWindow-getNativeWindowHandle-completed", Id); } /// @@ -1844,17 +1604,7 @@ namespace ElectronNET.API /// public Task GetRepresentedFilenameAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-getRepresentedFilename-completed", (pathname) => { - BridgeConnector.Off("browserWindow-getRepresentedFilename-completed"); - - taskCompletionSource.SetResult(pathname); - }); - - BridgeConnector.Emit("browserWindowGetRepresentedFilename", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowGetRepresentedFilename", "browserWindow-getRepresentedFilename-completed", Id); } /// @@ -1873,17 +1623,7 @@ namespace ElectronNET.API /// public Task IsDocumentEditedAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isDocumentEdited-completed", (edited) => { - BridgeConnector.Off("browserWindow-isDocumentEdited-completed"); - - taskCompletionSource.SetResult(edited); - }); - - BridgeConnector.Emit("browserWindowIsDocumentEdited", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsDocumentEdited", "browserWindow-isDocumentEdited-completed", Id); } /// @@ -1952,7 +1692,8 @@ namespace ElectronNET.API _items.AddRange(menuItems); BridgeConnector.Off("windowMenuItemClicked"); - BridgeConnector.On("windowMenuItemClicked", (id) => { + BridgeConnector.On("windowMenuItemClicked", (id) => + { MenuItem menuItem = _items.GetMenuItem(id); menuItem?.Click(); }); @@ -2016,17 +1757,7 @@ namespace ElectronNET.API /// public Task HasShadowAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-hasShadow-completed", (hasShadow) => { - BridgeConnector.Off("browserWindow-hasShadow-completed"); - - taskCompletionSource.SetResult(hasShadow); - }); - - BridgeConnector.Emit("browserWindowHasShadow", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowHasShadow", "browserWindow-hasShadow-completed", Id); } /// @@ -2036,6 +1767,7 @@ namespace ElectronNET.API /// The thumbar buttons. /// public IReadOnlyCollection ThumbarButtons { get { return _thumbarButtons.AsReadOnly(); } } + private List _thumbarButtons = new List(); /// @@ -2054,7 +1786,8 @@ namespace ElectronNET.API { var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - BridgeConnector.On("browserWindowSetThumbarButtons-completed", (success) => { + BridgeConnector.On("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("thumbarButtonClicked", (id) => { + BridgeConnector.On("thumbarButtonClicked", (id) => + { ThumbarButton thumbarButton = _thumbarButtons.GetThumbarButton(id); thumbarButton?.Click(); }); @@ -2132,17 +1866,7 @@ namespace ElectronNET.API /// public Task IsMenuBarAutoHideAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isMenuBarAutoHide-completed", (isMenuBarAutoHide) => { - BridgeConnector.Off("browserWindow-isMenuBarAutoHide-completed"); - - taskCompletionSource.SetResult(isMenuBarAutoHide); - }); - - BridgeConnector.Emit("browserWindowIsMenuBarAutoHide", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsMenuBarAutoHide", "browserWindow-isMenuBarAutoHide-completed", Id); } /// @@ -2161,17 +1885,7 @@ namespace ElectronNET.API /// public Task IsMenuBarVisibleAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isMenuBarVisible-completed", (isMenuBarVisible) => { - BridgeConnector.Off("browserWindow-isMenuBarVisible-completed"); - - taskCompletionSource.SetResult(isMenuBarVisible); - }); - - BridgeConnector.Emit("browserWindowIsMenuBarVisible", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsMenuBarVisible", "browserWindow-isMenuBarVisible-completed", Id); } /// @@ -2193,17 +1907,7 @@ namespace ElectronNET.API /// public Task IsVisibleOnAllWorkspacesAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-isVisibleOnAllWorkspaces-completed", (isVisibleOnAllWorkspaces) => { - BridgeConnector.Off("browserWindow-isVisibleOnAllWorkspaces-completed"); - - taskCompletionSource.SetResult(isVisibleOnAllWorkspaces); - }); - - BridgeConnector.Emit("browserWindowIsVisibleOnAllWorkspaces", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("browserWindowIsVisibleOnAllWorkspaces", "browserWindow-isVisibleOnAllWorkspaces-completed", Id); } /// @@ -2253,44 +1957,20 @@ namespace ElectronNET.API /// The parent window. /// /// - public Task GetParentWindowAsync() + public async Task GetParentWindowAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("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("browserWindowGetParentWindow", "browserWindow-getParentWindow-completed", Id); + return Electron.WindowManager.BrowserWindows.Where(w => w.Id == windowID).Single(); } /// /// All child windows. /// /// - public Task> GetChildWindowsAsync() + public async Task> GetChildWindowsAsync() { - var taskCompletionSource = new TaskCompletionSource>(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("browserWindow-getChildWindows-completed", (ids) => { - BridgeConnector.Off("browserWindow-getChildWindows-completed"); - var browserWindows = new List(); - 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(await BridgeConnector.OnResult("browserWindowGetChildWindows", "browserWindow-getChildWindows-completed", Id)); + return Electron.WindowManager.BrowserWindows.Where(w => windowIDs.Contains(w.Id)).ToList(); } /// @@ -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 diff --git a/ElectronNET.API/Dock.cs b/ElectronNET.API/Dock.cs index 82c264f..7410d80 100644 --- a/ElectronNET.API/Dock.cs +++ b/ElectronNET.API/Dock.cs @@ -50,24 +50,11 @@ namespace ElectronNET.API /// Can be critical or informational. The default is informational. /// The cancellation token. /// Return an ID representing the request. - public async Task BounceAsync(DockBounceType type, CancellationToken cancellationToken = default) + public Task BounceAsync(DockBounceType type, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled())) - { - BridgeConnector.On("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("dock-bounce", "dock-bounce-completed", cancellationToken, type.GetDescription()); } /// @@ -102,24 +89,10 @@ namespace ElectronNET.API /// /// The cancellation token. /// The badge string of the dock. - public async Task GetBadgeAsync(CancellationToken cancellationToken = default) + public Task GetBadgeAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled())) - { - BridgeConnector.On("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("dock-getBadge", "dock-getBadge-completed", cancellationToken); } /// @@ -144,24 +117,10 @@ namespace ElectronNET.API /// /// The cancellation token. /// Whether the dock icon is visible. - public async Task IsVisibleAsync(CancellationToken cancellationToken = default) + public Task IsVisibleAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled())) - { - BridgeConnector.On("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("dock-isVisible", "dock-isVisible-completed", cancellationToken); } /// @@ -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 diff --git a/ElectronNET.API/NativeTheme.cs b/ElectronNET.API/NativeTheme.cs index d9cb77c..db2a94b 100644 --- a/ElectronNET.API/NativeTheme.cs +++ b/ElectronNET.API/NativeTheme.cs @@ -126,17 +126,7 @@ namespace ElectronNET.API /// public Task ShouldUseDarkColorsAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("nativeTheme-shouldUseDarkColors-completed", (shouldUseDarkColors) => { - BridgeConnector.Off("nativeTheme-shouldUseDarkColors-completed"); - - taskCompletionSource.SetResult(shouldUseDarkColors); - }); - - BridgeConnector.Emit("nativeTheme-shouldUseDarkColors"); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("nativeTheme-shouldUseDarkColors", "nativeTheme-shouldUseDarkColors-completed"); } /// @@ -145,17 +135,7 @@ namespace ElectronNET.API /// public Task ShouldUseHighContrastColorsAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("nativeTheme-shouldUseHighContrastColors-completed", (shouldUseHighContrastColors) => { - BridgeConnector.Off("nativeTheme-shouldUseHighContrastColors-completed"); - - taskCompletionSource.SetResult(shouldUseHighContrastColors); - }); - - BridgeConnector.Emit("nativeTheme-shouldUseHighContrastColors"); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("nativeTheme-shouldUseHighContrastColors", "nativeTheme-shouldUseHighContrastColors-completed"); } /// @@ -164,17 +144,7 @@ namespace ElectronNET.API /// public Task ShouldUseInvertedColorSchemeAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("nativeTheme-shouldUseInvertedColorScheme-completed", (shouldUseInvertedColorScheme) => { - BridgeConnector.Off("nativeTheme-shouldUseInvertedColorScheme-completed"); - - taskCompletionSource.SetResult(shouldUseInvertedColorScheme); - }); - - BridgeConnector.Emit("nativeTheme-shouldUseInvertedColorScheme"); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("nativeTheme-shouldUseInvertedColorScheme", "nativeTheme-shouldUseInvertedColorScheme-completed"); } /// diff --git a/ElectronNET.API/Shell.cs b/ElectronNET.API/Shell.cs index d2e4447..89c5076 100644 --- a/ElectronNET.API/Shell.cs +++ b/ElectronNET.API/Shell.cs @@ -123,18 +123,7 @@ namespace ElectronNET.API /// Whether the item was successfully moved to the trash. public Task TrashItemAsync(string fullPath) { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("shell-trashItem-completed", (success) => - { - BridgeConnector.Off("shell-trashItem-completed"); - - taskCompletionSource.SetResult(success); - }); - - BridgeConnector.Emit("shell-trashItem", fullPath); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("shell-trashItem", "shell-trashItem-completed", fullPath); } /// @@ -154,18 +143,7 @@ namespace ElectronNET.API /// Whether the shortcut was created successfully. public Task WriteShortcutLinkAsync(string shortcutPath, ShortcutLinkOperation operation, ShortcutDetails options) { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("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("shell-writeShortcutLink", "shell-writeShortcutLinkCompleted", shortcutPath, operation.GetDescription(), JObject.FromObject(options, _jsonSerializer)); } /// @@ -176,17 +154,7 @@ namespace ElectronNET.API /// of the shortcut. public Task ReadShortcutLinkAsync(string shortcutPath) { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("shell-readShortcutLinkCompleted", (shortcutDetails) => - { - BridgeConnector.Off("shell-readShortcutLinkCompleted"); - taskCompletionSource.SetResult(shortcutDetails); - }); - - BridgeConnector.Emit("shell-readShortcutLink", shortcutPath); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("shell-readShortcutLink", "shell-readShortcutLinkCompleted", shortcutPath); } private readonly JsonSerializer _jsonSerializer = new JsonSerializer() diff --git a/ElectronNET.API/WebContents.cs b/ElectronNET.API/WebContents.cs index 8689c6c..6affad4 100644 --- a/ElectronNET.API/WebContents.cs +++ b/ElectronNET.API/WebContents.cs @@ -113,18 +113,7 @@ namespace ElectronNET.API /// printers public Task GetPrintersAsync() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("webContents-getPrinters-completed", (printers) => - { - BridgeConnector.Off("webContents-getPrinters-completed"); - - taskCompletionSource.SetResult(printers); - }); - - BridgeConnector.Emit("webContents-getPrinters", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("webContents-getPrinters", "webContents-getPrinters-completed", Id); } /// @@ -134,24 +123,7 @@ namespace ElectronNET.API /// success public Task PrintAsync(PrintOptions options = null) { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("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("webContents-print", "webContents-print-completed", Id, options is null ? (object)"" : JObject.FromObject(options, _jsonSerializer)); } /// @@ -165,24 +137,7 @@ namespace ElectronNET.API /// success public Task PrintToPDFAsync(string path, PrintToPDFOptions options = null) { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - BridgeConnector.On("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("webContents-printToPDF", "webContents-printToPDF-completed", Id, options is null ? (object)"" : JObject.FromObject(options, _jsonSerializer), path); } /// @@ -192,18 +147,7 @@ namespace ElectronNET.API /// URL of the loaded page public Task GetUrl() { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - var eventString = "webContents-getUrl" + Id; - BridgeConnector.On(eventString, (url) => - { - BridgeConnector.Off(eventString); - taskCompletionSource.SetResult(url); - }); - - BridgeConnector.Emit("webContents-getUrl", Id); - - return taskCompletionSource.Task; + return BridgeConnector.OnResult("webContents-getUrl", "webContents-getUrl" + Id, Id); } /// @@ -237,19 +181,20 @@ namespace ElectronNET.API /// /// public Task LoadURLAsync(string url, LoadURLOptions options) - { - var taskCompletionSource = new TaskCompletionSource(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("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,