Compare commits

...

12 Commits

Author SHA1 Message Date
Florian Rappl
d85a64f515 Merge pull request #1014 from softworkz/submit_new_event
BrowserWindow: Add OnBoundsChanged event
2026-01-21 08:19:16 +01:00
Florian Rappl
53698d1d44 Merge pull request #1013 from softworkz/submit_timeout
Double-up timeout for electron-builder and other fixes
2026-01-21 08:07:50 +01:00
Florian Rappl
04a224aa4f Merge pull request #1012 from softworkz/submit_migcheck_updates
Relax Migration check for package.json in root
2026-01-21 08:06:55 +01:00
softworkz
891da140b7 Relax Migration check for package.json in root 2026-01-21 06:50:07 +01:00
softworkz
17f761d184 Fix FormatException 2026-01-21 06:48:28 +01:00
softworkz
9f6489891e ElectronNET.Host: Revert JS SDK to default value from VS 2026 2026-01-21 06:48:28 +01:00
softworkz
c8f1cdf59f Add tests for BrowserWindow.OnBoundsChanged event 2026-01-21 06:47:28 +01:00
softworkz
6c95dfd476 BrowserWindow: Add OnBoundsChanged event
While not being an original Electron event, this one includes the bounds values,
saving the additional roundtrip for calling GetBoundsAsync
2026-01-21 06:47:28 +01:00
softworkz
b180fc2ea8 Double-up timeout for electron-builder 2026-01-21 05:10:24 +01:00
Florian Rappl
682a1c38ed Optional 2025-12-23 21:10:57 +01:00
Florian Rappl
809b0a6be7 Added missing methods 2025-12-23 00:38:37 +01:00
Florian Rappl
ae3f755648 Fixed center handling 2025-12-22 12:32:52 +01:00
21 changed files with 288 additions and 38 deletions

View File

@@ -1,3 +1,10 @@
# 0.4.1
## ElectronNET.Core
- Fixed handling of `Center` property for windows (#1001)
- Added missing methods on `Cookies` (#1000)
# 0.4.0
## ElectronNET.Core

View File

@@ -8,7 +8,9 @@ When you build an Electron.NET project, the following validation checks are perf
| Code | Check | Description |
|------|-------|-------------|
| [ELECTRON001](#1-packagejson-not-allowed) | package.json not allowed | Ensures no package.json exists outside ElectronHostHook |
| [ELECTRON001](#1-packagejson-rules) | package.json location rules | Ensures `package.json`/`package-lock.json` arent present in unsupported locations (root `package.json` handled separately) |
| [ELECTRON008](#1-packagejson-rules) | root package.json contains electron | Warns when root `package.json` contains the word `electron` (case-insensitive) |
| [ELECTRON009](#1-packagejson-rules) | root package.json copied to output | Warns when root `package.json` is configured to be copied to output/publish |
| [ELECTRON002](#2-electron-manifestjson-not-allowed) | electron-manifest.json not allowed | Detects deprecated manifest files |
| [ELECTRON003](#3-electron-builderjson-location) | electron-builder.json location | Verifies electron-builder.json exists in Properties folder |
| [ELECTRON004](#3-electron-builderjson-location) | electron-builder.json wrong location | Warns if electron-builder.json is found in incorrect locations |
@@ -18,24 +20,38 @@ When you build an Electron.NET project, the following validation checks are perf
---
## 1. package.json not allowed
## 1. package.json rules
**Warning Code:** `ELECTRON001`
**Warning Codes:** `ELECTRON001`, `ELECTRON008`, `ELECTRON009`
### What is checked
The build system scans for `package.json` and `package-lock.json` files in your project directory. These files should not exist in the project root or subdirectories (with one exception).
The build system scans for `package.json` and `package-lock.json` files in your project directory.
Rules:
- **ELECTRON001**: `package.json` / `package-lock.json` must not exist in the project directory or subdirectories
- Exception: `ElectronHostHook` folder is allowed
- Note: a **root** `package.json` is **excluded** from `ELECTRON001` and validated by `ELECTRON008` / `ELECTRON009`
- **ELECTRON008**: If a root `package.json` exists, it must **not** contain electron-related dependencies or configuration.
- **ELECTRON009**: If a root `package.json` exists, it must **not** be configured to be copied to output/publish (for example via `CopyToOutputDirectory` / `CopyToPublishDirectory` metadata)
### Why this matters
In previous versions of Electron.NET, a `package.json` file was required in the project. The new version generates this file automatically from MSBuild properties defined in your `.csproj` file.
Electron.NET generates its Electron-related `package.json` during publishing based on MSBuild properties. A user-maintained Electron-related `package.json` can conflict with that process.
Also, ensuring the root `package.json` is not copied prevents accidentally shipping it with the published app.
### Exception
A `package.json` file **is allowed** in the `ElectronHostHook` folder if you're using custom host hooks. This is the only valid location for a manually maintained package.json.
A `package.json` / `package-lock.json` file **is allowed** in the `ElectronHostHook` folder if you're using custom host hooks.
### How to fix
If you have an Electron-related `package.json` from older Electron.NET versions:
1. **Open your project's `.csproj` file**
2. **Add the required properties** to a PropertyGroup with the label `ElectronNetCommon`:
@@ -51,7 +67,12 @@ A `package.json` file **is allowed** in the `ElectronHostHook` folder if you're
</PropertyGroup>
```
3. **Delete the old `package.json`** file from your project root
3. **Delete** Electron-related `package.json` / `package-lock.json` files (except those under `ElectronHostHook` if applicable)
If you keep a root `package.json` for non-Electron reasons:
- Ensure it does **not** contain electron dependencies or configuration (fixes `ELECTRON008`)
- Ensure it is **not** copied to output/publish (fixes `ELECTRON009`)
> **See also:** [Migration Guide](Migration-Guide.md) for complete migration instructions.

View File

@@ -54,7 +54,7 @@ Add the Electron.NET configuration to your `.csproj` file:
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.4.0" />
<PackageReference Include="ElectronNET.Core" Version="0.4.1" />
</ItemGroup>
```

View File

@@ -314,7 +314,9 @@ namespace ElectronNET.API
{
if (this.tcs != null)
{
var ex = new TimeoutException($"No response after {timeout:D}ms trying to retrieve value {apiBase.objectName}.{callerName}()");
var ex = new TimeoutException(
$"No response after {(long)timeout.TotalMilliseconds}ms trying to retrieve value {apiBase.objectName}.{callerName}()"
);
this.tcs.TrySetException(ex);
this.tcs = null;
}

View File

@@ -186,6 +186,19 @@ public class BrowserWindow : ApiBase
remove => RemoveEvent(value, Id);
}
/// <summary>
/// Emitted when the window is moved or resized.
/// </summary>
/// <remarks>
/// While not being an original Electron event, this one includes the bounds values,
/// saving the additional roundtrip for calling <see cref="GetBoundsAsync"/>.
/// </remarks>
public event Action<Rectangle> OnBoundsChanged
{
add => AddEvent(value, Id);
remove => RemoveEvent(value, Id);
}
/// <summary>
/// macOS: Emitted once when the window is moved to a new position.
/// </summary>

View File

@@ -2,6 +2,7 @@ using ElectronNET.API.Entities;
using ElectronNET.API.Serialization;
using System;
using System.Text.Json;
using System.Threading.Tasks;
namespace ElectronNET.API
{
@@ -54,10 +55,79 @@ namespace ElectronNET.API
_changed -= value;
if (_changed == null)
{
BridgeConnector.Socket.Off("webContents-session-cookies-changed" + Id);
}
}
}
private event Action<Cookie, CookieChangedCause, bool> _changed;
/// <summary>
/// Sends a request to get all cookies matching filter, and resolves a callack with the response.
/// </summary>
/// <param name="filter">
/// </param>
/// <returns>A task which resolves an array of cookie objects.</returns>
public Task<Cookie[]> GetAsync(CookieFilter filter)
{
var tcs = new TaskCompletionSource<Cookie[]>();
var guid = Guid.NewGuid().ToString();
BridgeConnector.Socket.Once<Cookie[]>("webContents-session-cookies-get-completed" + guid, tcs.SetResult);
BridgeConnector.Socket.Emit("webContents-session-cookies-get", Id, filter, guid);
return tcs.Task;
}
/// <summary>
///
/// </summary>
/// <param name="details"></param>
/// <returns></returns>
public Task SetAsync(CookieDetails details)
{
var tcs = new TaskCompletionSource<object>();
var guid = Guid.NewGuid().ToString();
BridgeConnector.Socket.Once<object>("webContents-session-cookies-set-completed" + guid, tcs.SetResult);
BridgeConnector.Socket.Emit("webContents-session-cookies-set", Id, details, guid);
return tcs.Task;
}
/// <summary>
/// Removes the cookies matching url and name
/// </summary>
/// <param name="url">The URL associated with the cookie.</param>
/// <param name="name">The name of cookie to remove.</param>
/// <returns>A task which resolves when the cookie has been removed</returns>
public Task RemoveAsync(string url, string name)
{
var tcs = new TaskCompletionSource<object>();
var guid = Guid.NewGuid().ToString();
BridgeConnector.Socket.Once<object>("webContents-session-cookies-remove-completed" + guid, tcs.SetResult);
BridgeConnector.Socket.Emit("webContents-session-cookies-remove", Id, url, name, guid);
return tcs.Task;
}
/// <summary>
/// Writes any unwritten cookies data to disk.
/// </summary>
/// <returns>A task which resolves when the cookie store has been flushed</returns>
public Task FlushStoreAsync()
{
var tcs = new TaskCompletionSource<object>();
var guid = Guid.NewGuid().ToString();
BridgeConnector.Socket.Once<object>("webContents-session-cookies-flushStore-completed" + guid, tcs.SetResult);
BridgeConnector.Socket.Emit("webContents-session-cookies-flushStore", Id, guid);
return tcs.Task;
}
}
}

View File

@@ -24,13 +24,13 @@ namespace ElectronNET.API.Entities
/// ( if y is used) Window's left offset from screen. Default is to center the
/// window.
/// </summary>
public int X { get; set; } = -1;
public int? X { get; set; }
/// <summary>
/// ( if x is used) Window's top offset from screen. Default is to center the
/// window.
/// </summary>
public int Y { get; set; } = -1;
public int? Y { get; set; }
/// <summary>
/// The width and height would be used as web page's size, which means the actual

View File

@@ -24,7 +24,7 @@
/// <summary>
/// Gets or sets a value indicating whether the cookie is a host-only cookie; this will only be true if no domain was passed.
/// </summary>
public bool HostOnly { get; set; }
public bool? HostOnly { get; set; }
/// <summary>
/// Gets or sets the path of the cookie.
@@ -34,22 +34,22 @@
/// <summary>
/// Gets or sets a value indicating whether the cookie is marked as secure.
/// </summary>
public bool Secure { get; set; }
public bool? Secure { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the cookie is marked as HTTP only.
/// </summary>
public bool HttpOnly { get; set; }
public bool? HttpOnly { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the cookie is a session cookie or a persistent cookie with an expiration date.
/// </summary>
public bool Session { get; set; }
public bool? Session { get; set; }
/// <summary>
/// Gets or sets the expiration date of the cookie as the number of seconds since the UNIX epoch. Not provided for session cookies.
/// </summary>
public double ExpirationDate { get; set; }
public double? ExpirationDate { get; set; }
/// <summary>
/// Gets or sets the SameSite policy applied to this cookie. Can be "unspecified", "no_restriction", "lax" or "strict".

View File

@@ -29,16 +29,16 @@
/// <summary>
/// (optional) - Filters cookies by their Secure property.
/// </summary>
public bool Secure { get; set; }
public bool? Secure { get; set; }
/// <summary>
/// (optional) - Filters out session or persistent cookies.
/// </summary>
public bool Session { get; set; }
public bool? Session { get; set; }
/// <summary>
/// (optional) - Filters cookies by httpOnly.
/// </summary>
public bool HttpOnly { get; set; }
public bool? HttpOnly { get; set; }
}
}

View File

@@ -130,11 +130,8 @@ namespace ElectronNET.API
options.Height += 7;
}
if (options.X == -1 && options.Y == -1)
if (!options.X.HasValue && !options.Y.HasValue)
{
options.X = 0;
options.Y = 0;
await BridgeConnector.Socket.Emit("createBrowserWindow", options, loadUrl).ConfigureAwait(false);
}
else

View File

@@ -70,7 +70,7 @@
<ProjectReference Include="..\ElectronNET.API\ElectronNET.API.csproj" Condition="$(ElectronNetDevMode)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.4.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core" Version="0.4.1" Condition="'$(ElectronNetDevMode)' != 'true'" />
</ItemGroup>
<Import Project="..\ElectronNET\build\ElectronNET.Core.targets" Condition="$(ElectronNetDevMode)" />

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/1.0.4110890">
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/1.0.3864779">
<ItemGroup>
<None Include=".vscode\tasks.json" />
</ItemGroup>

View File

@@ -112,6 +112,12 @@ module.exports = (socket, app) => {
electronSocket.emit("browserWindow-move" + id);
});
});
socket.on("register-browserWindow-bounds-changed", (id) => {
const window = getWindowById(id);
const cb = () => electronSocket.emit("browserWindow-bounds-changed" + id, window.getBounds());
window.on("resize", cb);
window.on("move", cb);
});
socket.on("register-browserWindow-moved", (id) => {
getWindowById(id).on("moved", () => {
electronSocket.emit("browserWindow-moved" + id);

File diff suppressed because one or more lines are too long

View File

@@ -139,6 +139,13 @@ export = (socket: Socket, app: Electron.App) => {
});
});
socket.on("register-browserWindow-bounds-changed", (id) => {
const window = getWindowById(id);
const cb = () => electronSocket.emit("browserWindow-bounds-changed" + id, window.getBounds());
window.on("resize", cb);
window.on("move", cb);
});
socket.on("register-browserWindow-moved", (id) => {
getWindowById(id).on("moved", () => {
electronSocket.emit("browserWindow-moved" + id);

View File

@@ -257,5 +257,57 @@ namespace ElectronNET.IntegrationTests.Tests
win.SetDocumentEdited(false);
}
[IntegrationFact]
public async Task BoundsChanged_event_fires_with_updated_bounds()
{
BrowserWindow window = null;
try
{
window = await Electron.WindowManager.CreateWindowAsync(
new BrowserWindowOptions { Show = false, Width = 300, Height = 200 },
"about:blank");
var tcs = new TaskCompletionSource<Rectangle>(TaskCreationOptions.RunContinuationsAsynchronously);
window.OnBoundsChanged += bounds => tcs.TrySetResult(bounds);
await Task.Delay(500.ms());
var target = new Rectangle { X = 25, Y = 35, Width = 420, Height = 310 };
window.SetBounds(target);
var completed = await Task.WhenAny(tcs.Task, Task.Delay(3.seconds()));
completed.Should().Be(tcs.Task);
var observed = await tcs.Task;
observed.Width.Should().Be(target.Width);
observed.Height.Should().Be(target.Height);
}
finally
{
window?.Destroy();
}
}
[IntegrationFact]
public async Task BoundsChanged_event_can_fire_on_resize_of_existing_window()
{
var win = this.MainWindow;
var tcs = new TaskCompletionSource<Rectangle>(TaskCreationOptions.RunContinuationsAsynchronously);
win.OnBoundsChanged += bounds => tcs.TrySetResult(bounds);
await Task.Delay(500.ms());
win.SetBounds(new Rectangle { X = 10, Y = 10, Width = 560, Height = 440 });
var completed = await Task.WhenAny(tcs.Task, Task.Delay(3.seconds()));
completed.Should().Be(tcs.Task);
var boundsObserved = await tcs.Task;
boundsObserved.Width.Should().Be(560);
boundsObserved.Height.Should().Be(440);
}
}
}

View File

@@ -27,8 +27,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.2.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.2.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core" Version="0.4.1" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.4.1" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.9.3" />
</ItemGroup>

View File

@@ -76,8 +76,8 @@
<ProjectReference Include="..\ElectronNET.AspNet\ElectronNET.AspNet.csproj" Condition="$(ElectronNetDevMode)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.4.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.4.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core" Version="0.4.1" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.4.1" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.9.3" />
</ItemGroup>

View File

@@ -522,7 +522,7 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpxCmd)'</_NpxCmd>
</PropertyGroup>
<Exec Command="$(_NpxCmd)" WorkingDirectory="$(ElectronPublishDirFullPath)" Timeout="900000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(CopiedFiles->Count()) > 0">
<Exec Command="$(_NpxCmd)" WorkingDirectory="$(ElectronPublishDirFullPath)" Timeout="1800000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(CopiedFiles->Count()) > 0">
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
</Exec>

View File

@@ -4,6 +4,8 @@
<PropertyGroup>
<ElectronMigrationChecksDependsOn>
ElectronCheckNoPackageJson;
ElectronCheckRootPackageJsonNoElectron;
ElectronCheckRootPackageJsonNotCopied;
ElectronCheckNoManifestJson;
ElectronCheckElectronBuilderJson;
ElectronCheckNoParentPaths;
@@ -19,14 +21,17 @@
</Target>
<!--
Check 1: No package.json must be present in the project (except ElectronHostHook folder)
Check 1: No package.json/package-lock.json must be present in the project (except ElectronHostHook folder)
NOTE: Root package.json is excluded from ELECTRON001 and checked by separate targets.
-->
<Target Name="ElectronCheckNoPackageJson">
<!-- Find all package.json files, excluding ElectronHostHook folder and output directories -->
<!-- Find all package.json files, excluding ElectronHostHook folder, root, and output directories -->
<ItemGroup>
<_InvalidPackageJson Include="$(MSBuildProjectDirectory)\**\package.json"
Exclude="$(MSBuildProjectDirectory)\ElectronHostHook\**\package.json;
Exclude="$(MSBuildProjectDirectory)\package.json;
$(MSBuildProjectDirectory)\ElectronHostHook\**\package.json;
$(MSBuildProjectDirectory)\bin\**\package.json;
$(MSBuildProjectDirectory)\obj\**\package.json;
$(MSBuildProjectDirectory)\publish\**\package.json;
@@ -46,17 +51,87 @@
<Warning Condition="'$(_HasInvalidPackageJson)' == 'true'"
Code="ELECTRON001"
Text="Found package.json or package-lock.json file(s) in the project root or subdirectories. These files are no longer supported in this location.
Text="Found package.json or package-lock.json file(s) in unsupported location(s). These files are no longer supported in this location.
Files found:
@(_InvalidPackageJson, '%0A')@(_InvalidPackageLockJson, '%0A')
MIGRATION REQUIRED:
All properties from an existing package.json file must now be specified as MSBuild properties in the project file.
All Electron.NET-related properties from an existing package.json must now be specified as MSBuild properties in the project file.
For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migration-Checks#1-packagejson-not-allowed
EXCEPTION: package.json and package-lock.json files ARE allowed in the 'ElectronHostHook' folder for custom host hook implementations." />
EXCEPTION:
- package.json and package-lock.json files ARE allowed in the 'ElectronHostHook' folder for custom host hook implementations.
- A package.json in the project root is handled by separate migration checks." />
</Target>
<!--
Check 1b: Root package.json must not contain Electron-related configuration
-->
<Target Name="ElectronCheckRootPackageJsonNoElectron"
Condition="Exists('$(MSBuildProjectDirectory)\package.json')">
<ItemGroup>
<_RootPackageJsonLines Include="$([System.IO.File]::ReadAllLines('$(MSBuildProjectDirectory)\package.json'))" />
</ItemGroup>
<PropertyGroup>
<_RootPackageJsonContent>@(_RootPackageJsonLines, ' ')</_RootPackageJsonContent>
<_RootPackageJsonHasElectron>false</_RootPackageJsonHasElectron>
<_RootPackageJsonHasElectron Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(_RootPackageJsonContent)', 'electron', System.Text.RegularExpressions.RegexOptions.IgnoreCase))">true</_RootPackageJsonHasElectron>
</PropertyGroup>
<Warning
Condition="'$(_RootPackageJsonHasElectron)' == 'true'"
Code="ELECTRON008"
Text="The project contains a root package.json that references 'electron' (case-insensitive).
File:
$(MSBuildProjectDirectory)\package.json
MIGRATION REQUIRED:
Electron.NET configuration must be defined via MSBuild properties and electron-builder.json (not via a user-provided package.json).
HOW TO FIX:
- Remove Electron-related entries from the root package.json, or delete the file if it's only used for Electron configuration.
For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migration-Checks#1-packagejson-not-allowed" />
</Target>
<!--
Check 1c: Root package.json must not be copied to output/publish
-->
<Target Name="ElectronCheckRootPackageJsonNotCopied"
Condition="Exists('$(MSBuildProjectDirectory)\package.json')">
<ItemGroup>
<_RootPackageJsonFile Include="@(Content);@(None)"
Condition="'%(Identity)' == 'package.json' OR '%(Identity)' == '$(MSBuildProjectDirectory)\package.json'" />
</ItemGroup>
<PropertyGroup>
<_RootPackageJsonIsCopied>false</_RootPackageJsonIsCopied>
<_RootPackageJsonIsCopied Condition="'@(_RootPackageJsonFile)' != '' AND ( '%(_RootPackageJsonFile.CopyToOutputDirectory)' != '' OR '%(_RootPackageJsonFile.CopyToPublishDirectory)' != '' )">true</_RootPackageJsonIsCopied>
</PropertyGroup>
<Warning
Condition="'$(_RootPackageJsonIsCopied)' == 'true'"
Code="ELECTRON009"
Text="The project contains a root package.json that is configured to be copied to the output/publish directory.
File:
$(MSBuildProjectDirectory)\package.json
MIGRATION REQUIRED:
Root package.json must not be copied during build/publish.
HOW TO FIX:
- Remove CopyToOutputDirectory / CopyToPublishDirectory metadata for package.json in your project file.
For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migration-Checks#1-packagejson-not-allowed" />
</Target>

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>0.4.0</Version>
<Version>0.4.1</Version>
<PackageNamePrefix>ElectronNET.Core</PackageNamePrefix>
<Authors>Gregor Biswanger, Florian Rappl, softworkz</Authors>
<Product>Electron.NET</Product>