From 25af4a731425a5fa6e86ef93f3781090082dc8ef Mon Sep 17 00:00:00 2001 From: theolivenbaum Date: Thu, 2 Sep 2021 16:18:56 +0200 Subject: [PATCH 1/9] Update to Electron 14 --- .devops/build-nuget.yaml | 2 +- ElectronNET.API/Entities/WebPreferences.cs | 6 --- ElectronNET.Host/package-lock.json | 60 +++++++++++----------- ElectronNET.Host/package.json | 6 +-- 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/.devops/build-nuget.yaml b/.devops/build-nuget.yaml index d28e726..e3dffe1 100644 --- a/.devops/build-nuget.yaml +++ b/.devops/build-nuget.yaml @@ -1,5 +1,5 @@ variables: - PackageVersion: 13.5.1.$(Build.BuildId) + PackageVersion: 14.0.0.$(Build.BuildId) projectAPI: './ElectronNET.API/ElectronNET.API.csproj' projectCLI: './ElectronNET.CLI/ElectronNET.CLI.csproj' diff --git a/ElectronNET.API/Entities/WebPreferences.cs b/ElectronNET.API/Entities/WebPreferences.cs index ca5e24e..f236247 100644 --- a/ElectronNET.API/Entities/WebPreferences.cs +++ b/ElectronNET.API/Entities/WebPreferences.cs @@ -207,11 +207,5 @@ namespace ElectronNET.API.Entities /// [DefaultValue(false)] public bool WebviewTag { get; set; } = false; - - /// - /// Whether to enable the remote module. Defaults to false. - /// - [DefaultValue(false)] - public bool EnableRemoteModule { get; set; } = false; } } \ No newline at end of file diff --git a/ElectronNET.Host/package-lock.json b/ElectronNET.Host/package-lock.json index 0f24a4a..35065d5 100644 --- a/ElectronNET.Host/package-lock.json +++ b/ElectronNET.Host/package-lock.json @@ -31,9 +31,9 @@ } }, "@electron/get": { - "version": "1.12.4", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.4.tgz", - "integrity": "sha512-6nr9DbJPUR9Xujw6zD3y+rS95TyItEVM0NVjt1EehY2vUWfIgPiIPVHxCvaTS0xr2B+DRxovYVKbuOWqC35kjg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.13.0.tgz", + "integrity": "sha512-+SjZhRuRo+STTO1Fdhzqnv9D2ZhjxXP6egsJ9kiO8dtP68cDx7dFCwWi64dlMQV7sWcfW1OYCW4wviEBzmRsfQ==", "dev": true, "requires": { "debug": "^4.1.1", @@ -71,9 +71,9 @@ } }, "@types/node": { - "version": "15.14.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.0.tgz", - "integrity": "sha512-um/+/ip3QZmwLfIkWZSNtQIJNVAqrJ92OkLMeuZrjZMTAJniI7fh8N8OICyDhAJ2mzgk/fmYFo72jRr5HyZ1EQ==", + "version": "15.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", + "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==", "dev": true }, "@types/semver": { @@ -153,9 +153,9 @@ "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" }, "boolean": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.2.tgz", - "integrity": "sha512-YN6UmV0FfLlBVvRvNPx3pz5W/mUoYB24J4WSXOKP/OOJpi+Oq6WYqPaNTHzjI0QzwWtnvEd5CGYyQPgp1jFxnw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.4.tgz", + "integrity": "sha512-3hx0kwU3uzG6ReQ3pnaFQPSktpBw6RHN3/ivDKEuU8g1XSfafowyvDnadjv1xp8IZqhtSukxlwv9bF6FhX8m0w==", "dev": true, "optional": true }, @@ -176,9 +176,9 @@ "dev": true }, "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "builder-util-runtime": { @@ -337,16 +337,16 @@ "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" }, "core-js": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.15.2.tgz", - "integrity": "sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==", + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.17.1.tgz", + "integrity": "sha512-C8i/FNpVN2Ti89QIJcFn9ZQmnM+HaAQr2OpE+ja3TRM9Q34FigsGlAVuwPGkIgydSVClo/1l1D1grP8LVt9IYA==", "dev": true, "optional": true }, "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, "dasherize": { @@ -355,9 +355,9 @@ "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" @@ -408,9 +408,9 @@ "dev": true }, "electron": { - "version": "13.1.5", - "resolved": "https://registry.npmjs.org/electron/-/electron-13.1.5.tgz", - "integrity": "sha512-ZoMCcPQNs/zO/Zdb5hq5H+rwRaKrdI3/sfXEwBVMx7f5jwa9jPQB3dZ2+7t59uD9VcFAWsH/pozr8nPPlv0tyw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-14.0.0.tgz", + "integrity": "sha512-O6EI7L1BPIrTpEIFefjjmdbmSn9LtE4mmrv4dfpV4Mqaa8uKuNYQogwZPEvSwaBexb69eb1LQ25n+f+kBcjiRQ==", "dev": true, "requires": { "@electron/get": "^1.0.1", @@ -419,9 +419,9 @@ }, "dependencies": { "@types/node": { - "version": "14.17.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz", - "integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A==", + "version": "14.17.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.14.tgz", + "integrity": "sha512-rsAj2u8Xkqfc332iXV12SqIsjVi07H479bOP4q94NAcjzmAvapumEhuVIt53koEf7JFrpjgNKjBga5Pnn/GL8A==", "dev": true } } @@ -1410,9 +1410,9 @@ "dev": true }, "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", + "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", "dev": true }, "universalify": { diff --git a/ElectronNET.Host/package.json b/ElectronNET.Host/package.json index 628c152..d29cc1f 100644 --- a/ElectronNET.Host/package.json +++ b/ElectronNET.Host/package.json @@ -19,9 +19,9 @@ "socket.io": "^2.4.0" }, "devDependencies": { - "@types/node": "^15.14.0", - "electron": "^13.1.5", + "@types/node": "^15.14.9", + "electron": "^14.0.0", "tslint": "^6.1.3", - "typescript": "^4.3.5" + "typescript": "^4.4.2" } } From 5f4b1628e5a111780c0df5cd384e13cdaf607d57 Mon Sep 17 00:00:00 2001 From: theolivenbaum Date: Thu, 2 Sep 2021 16:34:20 +0200 Subject: [PATCH 2/9] fix display serialization, serialize only window id when sending ipc message --- ElectronNET.API/BridgeConnector.cs | 1 + ElectronNET.API/Entities/Display.cs | 4 ++-- ElectronNET.API/IpcMain.cs | 10 ++++++---- ElectronNET.Host/api/ipc.js | 4 ++-- ElectronNET.Host/api/ipc.ts | 4 ++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ElectronNET.API/BridgeConnector.cs b/ElectronNET.API/BridgeConnector.cs index 8a3a792..14a9def 100644 --- a/ElectronNET.API/BridgeConnector.cs +++ b/ElectronNET.API/BridgeConnector.cs @@ -151,6 +151,7 @@ namespace ElectronNET.API else { Console.WriteLine(formatString, args); + Console.WriteLine(E.ToString()); } } diff --git a/ElectronNET.API/Entities/Display.cs b/ElectronNET.API/Entities/Display.cs index 4d44f32..4d8cceb 100644 --- a/ElectronNET.API/Entities/Display.cs +++ b/ElectronNET.API/Entities/Display.cs @@ -26,12 +26,12 @@ /// /// Can be 0, 90, 180, 270, represents screen rotation in clock-wise degrees. /// - public int Rotation { get; set; } + public float Rotation { get; set; } /// /// Output device's pixel scale factor. /// - public int ScaleFactor { get; set; } + public float ScaleFactor { get; set; } /// /// Gets or sets the size. diff --git a/ElectronNET.API/IpcMain.cs b/ElectronNET.API/IpcMain.cs index a8416ff..42d31cb 100644 --- a/ElectronNET.API/IpcMain.cs +++ b/ElectronNET.API/IpcMain.cs @@ -182,10 +182,12 @@ namespace ElectronNET.API if(parameterObject.GetType().IsArray || parameterObject.GetType().IsGenericType && parameterObject is IEnumerable) { jarrays.Add(JArray.FromObject(parameterObject, _jsonSerializer)); - } else if(parameterObject.GetType().IsClass && !parameterObject.GetType().IsPrimitive && !(parameterObject is string)) + } + else if(parameterObject.GetType().IsClass && !parameterObject.GetType().IsPrimitive && !(parameterObject is string)) { jobjects.Add(JObject.FromObject(parameterObject, _jsonSerializer)); - } else if(parameterObject.GetType().IsPrimitive || (parameterObject is string)) + } + else if(parameterObject.GetType().IsPrimitive || (parameterObject is string)) { objects.Add(parameterObject); } @@ -193,11 +195,11 @@ namespace ElectronNET.API if(jobjects.Count > 0 || jarrays.Count > 0) { - BridgeConnector.Emit("sendToIpcRenderer", JObject.FromObject(browserWindow, _jsonSerializer), channel, jarrays.ToArray(), jobjects.ToArray(), objects.ToArray()); + BridgeConnector.Emit("sendToIpcRenderer", browserWindow.Id, channel, jarrays.ToArray(), jobjects.ToArray(), objects.ToArray()); } else { - BridgeConnector.Emit("sendToIpcRenderer", JObject.FromObject(browserWindow, _jsonSerializer), channel, data); + BridgeConnector.Emit("sendToIpcRenderer", browserWindow.Id, channel, data); } } diff --git a/ElectronNET.Host/api/ipc.js b/ElectronNET.Host/api/ipc.js index da637e5..a8e10fe 100644 --- a/ElectronNET.Host/api/ipc.js +++ b/ElectronNET.Host/api/ipc.js @@ -38,8 +38,8 @@ module.exports = (socket) => { socket.on('removeAllListenersIpcMainChannel', (channel) => { electron_1.ipcMain.removeAllListeners(channel); }); - socket.on('sendToIpcRenderer', (browserWindow, channel, ...data) => { - const window = electron_1.BrowserWindow.fromId(browserWindow.id); + socket.on('sendToIpcRenderer', (browserWindowId, channel, ...data) => { + const window = electron_1.BrowserWindow.fromId(browserWindowId); if (window) { window.webContents.send(channel, ...data); } diff --git a/ElectronNET.Host/api/ipc.ts b/ElectronNET.Host/api/ipc.ts index 8927ac9..db0be14 100644 --- a/ElectronNET.Host/api/ipc.ts +++ b/ElectronNET.Host/api/ipc.ts @@ -44,8 +44,8 @@ export = (socket: Socket) => { ipcMain.removeAllListeners(channel); }); - socket.on('sendToIpcRenderer', (browserWindow, channel, ...data) => { - const window = BrowserWindow.fromId(browserWindow.id); + socket.on('sendToIpcRenderer', (browserWindowId, channel, ...data) => { + const window = BrowserWindow.fromId(browserWindowId); if (window) { window.webContents.send(channel, ...data); From 4fae2aeef2a89cf7ffe113c9a5cb537ac9cc2c2c Mon Sep 17 00:00:00 2001 From: theolivenbaum Date: Thu, 2 Sep 2021 16:45:03 +0200 Subject: [PATCH 3/9] fix ipc data serialization --- ElectronNET.API/IpcMain.cs | 22 ++++++++-------------- ElectronNET.Host/api/ipc.js | 1 + 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/ElectronNET.API/IpcMain.cs b/ElectronNET.API/IpcMain.cs index 42d31cb..0beedfd 100644 --- a/ElectronNET.API/IpcMain.cs +++ b/ElectronNET.API/IpcMain.cs @@ -173,34 +173,28 @@ namespace ElectronNET.API /// Arguments data. public void Send(BrowserWindow browserWindow, string channel, params object[] data) { - List jobjects = new List(); - List jarrays = new List(); - List objects = new List(); + var objectsWithCorrectSerialization = new List(); + + objectsWithCorrectSerialization.Add(browserWindow.Id); + objectsWithCorrectSerialization.Add(channel); foreach (var parameterObject in data) { if(parameterObject.GetType().IsArray || parameterObject.GetType().IsGenericType && parameterObject is IEnumerable) { - jarrays.Add(JArray.FromObject(parameterObject, _jsonSerializer)); + objectsWithCorrectSerialization.Add(JArray.FromObject(parameterObject, _jsonSerializer)); } else if(parameterObject.GetType().IsClass && !parameterObject.GetType().IsPrimitive && !(parameterObject is string)) { - jobjects.Add(JObject.FromObject(parameterObject, _jsonSerializer)); + objectsWithCorrectSerialization.Add(JObject.FromObject(parameterObject, _jsonSerializer)); } else if(parameterObject.GetType().IsPrimitive || (parameterObject is string)) { - objects.Add(parameterObject); + objectsWithCorrectSerialization.Add(parameterObject); } } - if(jobjects.Count > 0 || jarrays.Count > 0) - { - BridgeConnector.Emit("sendToIpcRenderer", browserWindow.Id, channel, jarrays.ToArray(), jobjects.ToArray(), objects.ToArray()); - } - else - { - BridgeConnector.Emit("sendToIpcRenderer", browserWindow.Id, channel, data); - } + BridgeConnector.Emit("sendToIpcRenderer", objectsWithCorrectSerialization.ToArray()); } /// diff --git a/ElectronNET.Host/api/ipc.js b/ElectronNET.Host/api/ipc.js index a8e10fe..ec17143 100644 --- a/ElectronNET.Host/api/ipc.js +++ b/ElectronNET.Host/api/ipc.js @@ -40,6 +40,7 @@ module.exports = (socket) => { }); socket.on('sendToIpcRenderer', (browserWindowId, channel, ...data) => { const window = electron_1.BrowserWindow.fromId(browserWindowId); + console.log(data); if (window) { window.webContents.send(channel, ...data); } From c229d497655fcedec60409d993584f1d30d533cd Mon Sep 17 00:00:00 2001 From: theolivenbaum Date: Thu, 2 Sep 2021 16:45:20 +0200 Subject: [PATCH 4/9] remove debug message --- ElectronNET.Host/api/ipc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ElectronNET.Host/api/ipc.js b/ElectronNET.Host/api/ipc.js index ec17143..a8e10fe 100644 --- a/ElectronNET.Host/api/ipc.js +++ b/ElectronNET.Host/api/ipc.js @@ -40,7 +40,6 @@ module.exports = (socket) => { }); socket.on('sendToIpcRenderer', (browserWindowId, channel, ...data) => { const window = electron_1.BrowserWindow.fromId(browserWindowId); - console.log(data); if (window) { window.webContents.send(channel, ...data); } From bb8965fa912ca3dae943b66916ee5d460a0a14c1 Mon Sep 17 00:00:00 2001 From: theolivenbaum Date: Thu, 2 Sep 2021 17:24:07 +0200 Subject: [PATCH 5/9] remove semaphore in favor of unique events per window /view create request --- ElectronNET.API/WindowManager.cs | 148 ++++++++++++------------- ElectronNET.Host/api/browserView.js | 4 +- ElectronNET.Host/api/browserView.ts | 4 +- ElectronNET.Host/api/browserWindows.js | 9 +- ElectronNET.Host/api/browserWindows.ts | 8 +- 5 files changed, 85 insertions(+), 88 deletions(-) diff --git a/ElectronNET.API/WindowManager.cs b/ElectronNET.API/WindowManager.cs index 65aea6b..60f4a1c 100644 --- a/ElectronNET.API/WindowManager.cs +++ b/ElectronNET.API/WindowManager.cs @@ -40,7 +40,6 @@ namespace ElectronNET.API return _windowManager; } } - private readonly SemaphoreSlim _singleCreate = new SemaphoreSlim(1, 1); /// /// Quit when all windows are closed. (Default is true) @@ -96,76 +95,85 @@ namespace ElectronNET.API /// public async Task CreateWindowAsync(BrowserWindowOptions options, string loadUrl = "http://localhost") { - await _singleCreate.WaitAsync(); + BootstrapUpdateOpenIDsEvent(); - try + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var guid = Guid.NewGuid().ToString(); + + BridgeConnector.Once("BrowserWindowCreated" + guid, (id) => { - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var browserWindow = new BrowserWindow(id); - BridgeConnector.On("BrowserWindowCreated", (id) => - { - BridgeConnector.Off("BrowserWindowCreated"); + _browserWindows[id] = browserWindow; - var browserWindow = new BrowserWindow(id); + taskCompletionSource.SetResult(browserWindow); + }); - _browserWindows[id] = browserWindow; - taskCompletionSource.SetResult(browserWindow); - }); + if (string.Equals(loadUrl, "HTTP://LOCALHOST", StringComparison.InvariantCultureIgnoreCase)) + { + loadUrl = $"{loadUrl}:{BridgeSettings.WebPort}"; + } - BridgeConnector.Off("BrowserWindowClosed"); - BridgeConnector.On("BrowserWindowClosed", (browserWindowIdsStillOpen) => - { - if (browserWindowIdsStillOpen.Any()) - { - foreach (var id in _browserWindows.Keys.ToArray()) - { - if (!browserWindowIdsStillOpen.Contains(id)) _browserWindows.TryRemove(id, out _); - } - } - else - { - _browserWindows.Clear(); - } - }); + // Workaround Windows 10 / Electron Bug + // https://github.com/electron/electron/issues/4045 + if (IsWindows10()) + { + options.Width += 14; + options.Height += 7; + } - if (string.Equals(loadUrl, "HTTP://LOCALHOST", StringComparison.InvariantCultureIgnoreCase)) - { - loadUrl = $"{loadUrl}:{BridgeSettings.WebPort}"; - } + if (options.X == -1 && options.Y == -1) + { + options.X = 0; + options.Y = 0; + BridgeConnector.Emit("createBrowserWindow", guid, JObject.FromObject(options, _jsonSerializer), loadUrl); + } + else + { // Workaround Windows 10 / Electron Bug // https://github.com/electron/electron/issues/4045 if (IsWindows10()) { - options.Width = options.Width + 14; - options.Height = options.Height + 7; + options.X -= 7; } - if (options.X == -1 && options.Y == -1) - { - options.X = 0; - options.Y = 0; - - BridgeConnector.Emit("createBrowserWindow", JObject.FromObject(options, _jsonSerializer), loadUrl); - } - else - { - // Workaround Windows 10 / Electron Bug - // https://github.com/electron/electron/issues/4045 - if (IsWindows10()) - { - options.X = options.X - 7; - } - - BridgeConnector.Emit("createBrowserWindow", JObject.FromObject(options, _keepDefaultValuesSerializer), loadUrl); - } - - return await taskCompletionSource.Task; + BridgeConnector.Emit("createBrowserWindow", guid, JObject.FromObject(options, _keepDefaultValuesSerializer), loadUrl); } - finally + + return await taskCompletionSource.Task; + } + + + private bool _hasClosedEvent = false; + private readonly object _hasClosedEventLock = new(); + private void BootstrapUpdateOpenIDsEvent() + { + if (!_hasClosedEvent) { - _singleCreate.Release(); + lock(_hasClosedEventLock) + { + if(!_hasClosedEvent) + { + BridgeConnector.On("BrowserWindowUpdateOpenIDs", (browserWindowIdsStillOpen) => + { + if (browserWindowIdsStillOpen.Any()) + { + foreach (var id in _browserWindows.Keys.ToArray()) + { + if (!browserWindowIdsStillOpen.Contains(id)) _browserWindows.TryRemove(id, out _); + } + } + else + { + _browserWindows.Clear(); + } + }); + _hasClosedEvent = true; + } + } } } @@ -194,32 +202,20 @@ namespace ElectronNET.API /// public async Task CreateBrowserViewAsync(BrowserViewConstructorOptions options) { - await _singleCreate.WaitAsync(); + var guid = Guid.NewGuid().ToString(); - try + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + BridgeConnector.Once("BrowserViewCreated" + guid, (id) => { + var browserView = new BrowserView(id); + _browserViews[id] = browserView; + taskCompletionSource.SetResult(browserView); + }); - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + BridgeConnector.Emit("createBrowserView", guid, JObject.FromObject(options, _keepDefaultValuesSerializer)); - BridgeConnector.On("BrowserViewCreated", (id) => - { - BridgeConnector.Off("BrowserViewCreated"); - - BrowserView browserView = new BrowserView(id); - - _browserViews[id] = browserView; - - taskCompletionSource.SetResult(browserView); - }); - - BridgeConnector.Emit("createBrowserView", JObject.FromObject(options, _keepDefaultValuesSerializer)); - - return await taskCompletionSource.Task; - } - finally - { - _singleCreate.Release(); - } + return await taskCompletionSource.Task; } private static JsonSerializer _jsonSerializer = new JsonSerializer() diff --git a/ElectronNET.Host/api/browserView.js b/ElectronNET.Host/api/browserView.js index b0d23cc..b992b17 100644 --- a/ElectronNET.Host/api/browserView.js +++ b/ElectronNET.Host/api/browserView.js @@ -7,7 +7,7 @@ let browserView, electronSocket; const proxyToCredentialsMap = (global['proxyToCredentialsMap'] = global['proxyToCredentialsMap'] || []); const browserViewApi = (socket) => { electronSocket = socket; - socket.on('createBrowserView', (options) => { + socket.on('createBrowserView', (guid, options) => { if (!hasOwnChildreen(options, 'webPreferences', 'nodeIntegration')) { options = { ...options, webPreferences: { nodeIntegration: true, contextIsolation: false } }; } @@ -20,7 +20,7 @@ const browserViewApi = (socket) => { proxyToCredentialsMap[options.proxy] = options.proxyCredentials; } browserViews.push(browserView); - electronSocket.emit('BrowserViewCreated', browserView['id']); + electronSocket.emit('BrowserViewCreated' + guid, browserView['id']); }); socket.on('browserView-getBounds', (id) => { const bounds = getBrowserViewById(id).getBounds(); diff --git a/ElectronNET.Host/api/browserView.ts b/ElectronNET.Host/api/browserView.ts index 6ccf33c..49cd240 100644 --- a/ElectronNET.Host/api/browserView.ts +++ b/ElectronNET.Host/api/browserView.ts @@ -7,7 +7,7 @@ const proxyToCredentialsMap: { [proxy: string]: string } = (global['proxyToCrede const browserViewApi = (socket: Socket) => { electronSocket = socket; - socket.on('createBrowserView', (options) => { + socket.on('createBrowserView', (guid, options) => { if (!hasOwnChildreen(options, 'webPreferences', 'nodeIntegration')) { options = { ...options, webPreferences: { nodeIntegration: true, contextIsolation: false } }; } @@ -25,7 +25,7 @@ const browserViewApi = (socket: Socket) => { browserViews.push(browserView); - electronSocket.emit('BrowserViewCreated', browserView['id']); + electronSocket.emit('BrowserViewCreated' + guid, browserView['id']); }); socket.on('browserView-getBounds', (id) => { diff --git a/ElectronNET.Host/api/browserWindows.js b/ElectronNET.Host/api/browserWindows.js index bbf86e3..458c4d9 100644 --- a/ElectronNET.Host/api/browserWindows.js +++ b/ElectronNET.Host/api/browserWindows.js @@ -175,7 +175,7 @@ module.exports = (socket, app) => { electronSocket.emit('browserWindow-new-window-for-tab' + id); }); }); - socket.on('createBrowserWindow', (options, loadUrl) => { + socket.on('createBrowserWindow', (guid, options, loadUrl) => { if (options.webPreferences && !('nodeIntegration' in options.webPreferences)) { options = { ...options, webPreferences: { ...options.webPreferences, nodeIntegration: true, contextIsolation: false } }; } @@ -188,7 +188,7 @@ module.exports = (socket, app) => { if (window) { window.reload(); windows.push(window); - electronSocket.emit('BrowserWindowCreated', window.id); + electronSocket.emit('BrowserWindowCreated' + guid, window.id); return; } } @@ -218,6 +218,7 @@ module.exports = (socket, app) => { readyToShowWindowsIds.push(window.id); } }); + window.on('closed', (sender) => { again: for (let index = 0; index < windows.length; index++) { @@ -234,7 +235,7 @@ module.exports = (socket, app) => { } const ids = []; windows.forEach(x => ids.push(x.id)); - electronSocket.emit('BrowserWindowClosed', ids); + electronSocket.emit('BrowserWindowUpdateOpenIDs', ids); }); if (loadUrl) { window.loadURL(loadUrl); @@ -250,7 +251,7 @@ module.exports = (socket, app) => { app['mainWindow'] = window; } windows.push(window); - electronSocket.emit('BrowserWindowCreated', window.id); + electronSocket.emit('BrowserWindowCreated' + guid, window.id); }); socket.on('browserWindowDestroy', (id) => { getWindowById(id)?.destroy(); diff --git a/ElectronNET.Host/api/browserWindows.ts b/ElectronNET.Host/api/browserWindows.ts index 14e0ce9..c1923a1 100644 --- a/ElectronNET.Host/api/browserWindows.ts +++ b/ElectronNET.Host/api/browserWindows.ts @@ -209,7 +209,7 @@ export = (socket: Socket, app: Electron.App) => { }); }); - socket.on('createBrowserWindow', (options, loadUrl) => { + socket.on('createBrowserWindow', (guid, options, loadUrl) => { if (options.webPreferences && !('nodeIntegration' in options.webPreferences)) { options = { ...options, webPreferences: { ...options.webPreferences, nodeIntegration: true, contextIsolation: false } }; } else if (!options.webPreferences) { @@ -222,7 +222,7 @@ export = (socket: Socket, app: Electron.App) => { if (window) { window.reload(); windows.push(window); - electronSocket.emit('BrowserWindowCreated', window.id); + electronSocket.emit('BrowserWindowCreated' + guid, window.id); return; } } else { @@ -261,7 +261,7 @@ export = (socket: Socket, app: Electron.App) => { } const ids = []; windows.forEach(x => ids.push(x.id)); - electronSocket.emit('BrowserWindowClosed', ids); + electronSocket.emit('BrowserWindowUpdateOpenIDs', ids); }); if (loadUrl) { @@ -281,7 +281,7 @@ export = (socket: Socket, app: Electron.App) => { } windows.push(window); - electronSocket.emit('BrowserWindowCreated', window.id); + electronSocket.emit('BrowserWindowCreated' + guid, window.id); }); socket.on('browserWindowDestroy', (id) => { From 47f4516ae25e0dd772990a688505a16c0edf6ee3 Mon Sep 17 00:00:00 2001 From: theolivenbaum Date: Thu, 2 Sep 2021 18:20:11 +0200 Subject: [PATCH 6/9] Add flag to check if the socket is connected --- ElectronNET.API/BridgeConnector.cs | 8 +++++--- ElectronNET.API/IpcMain.cs | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ElectronNET.API/BridgeConnector.cs b/ElectronNET.API/BridgeConnector.cs index 14a9def..1a36934 100644 --- a/ElectronNET.API/BridgeConnector.cs +++ b/ElectronNET.API/BridgeConnector.cs @@ -95,6 +95,8 @@ namespace ElectronNET.API private static Task _waitForConnection => _waitForBeingConnected.Task; + public static bool IsConnected => _waitForConnection is Task task && task.IsCompletedSuccessfully; + public static void Emit(string eventString, params object[] args) { //We don't care about waiting for the event to be emitted, so this doesn't need to be async @@ -387,14 +389,14 @@ namespace ElectronNET.API socket.OnConnected += (_, __) => { - Log("ElectronNET socket connected on port {0}!", BridgeSettings.SocketPort); _waitForBeingConnected.TrySetResult(); + Log("ElectronNET socket connected on port {0}!", BridgeSettings.SocketPort); }; socket.OnReconnectAttempt += (_, __) => { - Log("ElectronNET socket is trying to reconnect on port {0}...", BridgeSettings.SocketPort); _waitForBeingConnected = new(); + Log("ElectronNET socket is trying to reconnect on port {0}...", BridgeSettings.SocketPort); }; socket.OnReconnectError += (_, ex) => @@ -404,8 +406,8 @@ namespace ElectronNET.API socket.OnReconnected += (_, __) => { - Log("ElectronNET socket reconnected on port {0}...", BridgeSettings.SocketPort); _waitForBeingConnected.TrySetResult(); + Log("ElectronNET socket reconnected on port {0}...", BridgeSettings.SocketPort); }; diff --git a/ElectronNET.API/IpcMain.cs b/ElectronNET.API/IpcMain.cs index 0beedfd..537f1fb 100644 --- a/ElectronNET.API/IpcMain.cs +++ b/ElectronNET.API/IpcMain.cs @@ -37,6 +37,8 @@ namespace ElectronNET.API } } + public bool IsConnected => BridgeConnector.IsConnected; + /// /// Listens to channel, when a new message arrives listener would be called with /// listener(event, args...). @@ -62,7 +64,6 @@ namespace ElectronNET.API }); } - /// /// Listens to channel, when a new message arrives listener would be called with /// listener(event, args...). This listner will keep the window event sender id From d18700f64d0f71ab85b1f0de5a87ae068b840892 Mon Sep 17 00:00:00 2001 From: theolivenbaum Date: Thu, 2 Sep 2021 18:29:02 +0200 Subject: [PATCH 7/9] missing static flag --- ElectronNET.API/IpcMain.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ElectronNET.API/IpcMain.cs b/ElectronNET.API/IpcMain.cs index 537f1fb..d6e1eee 100644 --- a/ElectronNET.API/IpcMain.cs +++ b/ElectronNET.API/IpcMain.cs @@ -37,7 +37,7 @@ namespace ElectronNET.API } } - public bool IsConnected => BridgeConnector.IsConnected; + public static bool IsConnected => BridgeConnector.IsConnected; /// /// Listens to channel, when a new message arrives listener would be called with From 7f5db58edd0113904e0db4e37071795845eece0f Mon Sep 17 00:00:00 2001 From: theolivenbaum Date: Thu, 2 Sep 2021 20:06:01 +0200 Subject: [PATCH 8/9] fix deadlock case on emit before any Socket use --- ElectronNET.API/BridgeConnector.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ElectronNET.API/BridgeConnector.cs b/ElectronNET.API/BridgeConnector.cs index 1a36934..06db94d 100644 --- a/ElectronNET.API/BridgeConnector.cs +++ b/ElectronNET.API/BridgeConnector.cs @@ -93,7 +93,17 @@ namespace ElectronNET.API private static TaskCompletionSource _waitForBeingConnected = new TaskCompletionSource(); - private static Task _waitForConnection => _waitForBeingConnected.Task; + private static Task _waitForConnection + { + get + { + if(_socket is null) + { + var _ = Socket; //Ensure we trigger the first connection before anything else + } + return _waitForBeingConnected.Task; + } + } public static bool IsConnected => _waitForConnection is Task task && task.IsCompletedSuccessfully; @@ -414,7 +424,7 @@ namespace ElectronNET.API socket.OnDisconnected += async (_, reason) => { Log("ElectronNET socket disconnected with reason {0}, trying to reconnect on port {1}!", reason, BridgeSettings.SocketPort); - + _waitForBeingConnected = new(); int i = 0; From ea40249a9c4f28740b8fc6d4909cf54fb357f94f Mon Sep 17 00:00:00 2001 From: theolivenbaum Date: Thu, 2 Sep 2021 20:37:22 +0200 Subject: [PATCH 9/9] refactor Socket to use a task completion source instead of a getter with locks --- ElectronNET.API/BridgeConnector.cs | 253 +++++++++++++++-------------- 1 file changed, 130 insertions(+), 123 deletions(-) diff --git a/ElectronNET.API/BridgeConnector.cs b/ElectronNET.API/BridgeConnector.cs index 06db94d..b71708a 100644 --- a/ElectronNET.API/BridgeConnector.cs +++ b/ElectronNET.API/BridgeConnector.cs @@ -90,18 +90,15 @@ namespace ElectronNET.API private static SemaphoreSlim _socketSemaphoreEmit = new SemaphoreSlim(1, 1); private static SemaphoreSlim _socketSemaphoreHandlers = new SemaphoreSlim(1, 1); - - private static TaskCompletionSource _waitForBeingConnected = new TaskCompletionSource(); - private static Task _waitForConnection + private static TaskCompletionSource _connectedSocketTask = new TaskCompletionSource(); + + private static Task _waitForConnection { get { - if(_socket is null) - { - var _ = Socket; //Ensure we trigger the first connection before anything else - } - return _waitForBeingConnected.Task; + EnsureSocketTaskIsCreated(); + return _connectedSocketTask.Task; } } @@ -111,25 +108,23 @@ namespace ElectronNET.API { //We don't care about waiting for the event to be emitted, so this doesn't need to be async - Task.Run(async () => - { - await EmitAsync(eventString, args); - }); + Task.Run(() => EmitAsync(eventString, args)); } private static async Task EmitAsync(string eventString, object[] args) { - await _waitForConnection; - if (App.SocketDebug) { Log("Sending event {0}", eventString); } + var socket = await _waitForConnection; + await _socketSemaphoreEmit.WaitAsync(); + try { - await Socket.EmitAsync(eventString, args); + await socket.EmitAsync(eventString, args); } finally { @@ -142,31 +137,6 @@ namespace ElectronNET.API } } - internal static void Log(string formatString, params object[] args) - { - if (Logger is object) - { - Logger.LogInformation(formatString, args); - } - else - { - Console.WriteLine(formatString, args); - } - } - - internal static void LogError(Exception E, string formatString, params object[] args) - { - if (Logger is object) - { - Logger.LogError(E, formatString, args); - } - else - { - Console.WriteLine(formatString, args); - Console.WriteLine(E.ToString()); - } - } - /// /// This method is only used on places where we need to be sure the event was sent on the socket, such as Quit, Exit, Relaunch and QuitAndInstall methods /// @@ -179,18 +149,19 @@ namespace ElectronNET.API Log("Sending event {0}", eventString); } - _waitForConnection.Wait(); - - _socketSemaphoreEmit.Wait(); - - try + Task.Run(async () => { - Socket.EmitAsync(eventString, args).Wait(); - } - finally - { - _socketSemaphoreEmit.Release(); - } + var socket = await _waitForConnection; + try + { + await _socketSemaphoreEmit.WaitAsync(); + await socket.EmitAsync(eventString, args); + } + finally + { + _socketSemaphoreEmit.Release(); + } + }).Wait(); if (App.SocketDebug) @@ -201,10 +172,12 @@ namespace ElectronNET.API public static void Off(string eventString) { + EnsureSocketTaskIsCreated(); + _socketSemaphoreHandlers.Wait(); try { - Socket.Off(eventString); + _socket.Off(eventString); } finally { @@ -214,10 +187,12 @@ namespace ElectronNET.API public static void On(string eventString, Action fn) { + EnsureSocketTaskIsCreated(); + _socketSemaphoreHandlers.Wait(); try { - Socket.On(eventString, _ => + _socket.On(eventString, _ => { try { @@ -237,10 +212,12 @@ namespace ElectronNET.API public static void On(string eventString, Action fn) { + EnsureSocketTaskIsCreated(); + _socketSemaphoreHandlers.Wait(); try { - Socket.On(eventString, (o) => + _socket.On(eventString, (o) => { try { @@ -372,101 +349,131 @@ namespace ElectronNET.API return await taskCompletionSource.Task; } - private static SocketIO Socket + + internal static void Log(string formatString, params object[] args) { - get + if (Logger is object) { - if (_socket is null) + Logger.LogInformation(formatString, args); + } + else + { + Console.WriteLine(formatString, args); + } + } + + internal static void LogError(Exception E, string formatString, params object[] args) + { + if (Logger is object) + { + Logger.LogError(E, formatString, args); + } + else + { + Console.WriteLine(formatString, args); + Console.WriteLine(E.ToString()); + } + } + + private static void EnsureSocketTaskIsCreated() + { + if (_socket is null) + { + if (HybridSupport.IsElectronActive) { - if (HybridSupport.IsElectronActive) + lock (_syncRoot) { - lock (_syncRoot) + if (_socket is null && HybridSupport.IsElectronActive) { - if (_socket is null && HybridSupport.IsElectronActive) + var socket = new SocketIO($"http://localhost:{BridgeSettings.SocketPort}", new SocketIOOptions() { - var socket = new SocketIO($"http://localhost:{BridgeSettings.SocketPort}", new SocketIOOptions() - { - EIO = 3, - Reconnection = true, - ReconnectionAttempts = int.MaxValue, - ReconnectionDelay = 1000, - ReconnectionDelayMax = 5000, - RandomizationFactor = 0.1, - ConnectionTimeout = TimeSpan.FromSeconds(10) - }); + EIO = 3, + Reconnection = true, + ReconnectionAttempts = int.MaxValue, + ReconnectionDelay = 1000, + ReconnectionDelayMax = 5000, + RandomizationFactor = 0.5, + ConnectionTimeout = TimeSpan.FromSeconds(10) + }); - socket.JsonSerializer = new CamelCaseNewtonsoftJsonSerializer(socket.Options.EIO); + socket.JsonSerializer = new CamelCaseNewtonsoftJsonSerializer(socket.Options.EIO); - socket.OnConnected += (_, __) => - { - _waitForBeingConnected.TrySetResult(); - Log("ElectronNET socket connected on port {0}!", BridgeSettings.SocketPort); - }; + socket.OnConnected += (_, __) => + { + _connectedSocketTask.TrySetResult(socket); + Log("ElectronNET socket connected on port {0}!", BridgeSettings.SocketPort); + }; - socket.OnReconnectAttempt += (_, __) => - { - _waitForBeingConnected = new(); - Log("ElectronNET socket is trying to reconnect on port {0}...", BridgeSettings.SocketPort); - }; + socket.OnReconnectAttempt += (_, __) => + { + _connectedSocketTask = new(); + Log("ElectronNET socket is trying to reconnect on port {0}...", BridgeSettings.SocketPort); + }; - socket.OnReconnectError += (_, ex) => - { - Log("ElectronNET socket failed to connect {0}", ex); - }; + socket.OnReconnectError += (_, ex) => + { + Log("ElectronNET socket failed to connect {0}", ex); + }; - socket.OnReconnected += (_, __) => - { - _waitForBeingConnected.TrySetResult(); - Log("ElectronNET socket reconnected on port {0}...", BridgeSettings.SocketPort); - }; + socket.OnReconnected += (_, __) => + { + _connectedSocketTask.TrySetResult(socket); + Log("ElectronNET socket reconnected on port {0}...", BridgeSettings.SocketPort); + }; - socket.OnDisconnected += async (_, reason) => - { - Log("ElectronNET socket disconnected with reason {0}, trying to reconnect on port {1}!", reason, BridgeSettings.SocketPort); + socket.OnDisconnected += async (_, reason) => + { + _connectedSocketTask = new(); - _waitForBeingConnected = new(); + Log("ElectronNET socket disconnected with reason {0}, trying to reconnect on port {1}!", reason, BridgeSettings.SocketPort); - int i = 0; + int i = 0; - double miliseconds = 500; + double miliseconds = 500; - while (true) + while (true) + { + try { - try + if (!socket.Connected) { - if (!socket.Connected) - { - await socket.ConnectAsync(); - _waitForBeingConnected.TrySetResult(); //Probably was already on the OnConnected call - } - return; + await socket.ConnectAsync(); + _connectedSocketTask.TrySetResult(socket); //Probably was already on the OnConnected call } - catch (Exception e) - { - LogError(e, "Failed to reconnect, will try again in {0} ms.", miliseconds * 2); - } - - await Task.Delay(TimeSpan.FromMilliseconds(miliseconds)); - - miliseconds = Math.Min(60_000, Math.Pow(2, i) + 500); - i++; + return; + } + catch (Exception e) + { + LogError(e, "Failed to reconnect, will try again in {0} ms.", miliseconds * 2); } - }; - socket.ConnectAsync().Wait(); + await Task.Delay(TimeSpan.FromMilliseconds(miliseconds)); - _socket = socket; - } + miliseconds = Math.Min(60_000, Math.Pow(2, i) + 500); + + i++; + } + }; + + Task.Run(async () => + { + await socket.ConnectAsync(); + _connectedSocketTask.TrySetResult(socket); //Probably was already on the OnConnected call + }); + + _socket = socket; + } + else + { + throw new Exception("Missing Socket Port"); } } - else - { - throw new Exception("Missing Socket Port"); - } } - - return _socket; + else + { + throw new Exception("Missing Socket Port"); + } } }