("head::after");
+});
builder.Build().Run();
```
@@ -71,18 +72,18 @@ builder.Build().Run();
When loading external module origins, explicitly trust them via window builder policy:
```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
+var app = InfiniFrameApplication.CreateBuilder().UseBlazorWebView(config => config.ConfigureWindow(wb => {
wb.AddTrustedOrigin("https://xyz");
// add redirects too if needed (e.g. cdn.jsdelivr.net, unpkg.com, etc.)
-});
+})).Build();
```
If you must allow every origin (not recommended outside local/dev scenarios):
```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
+var app = InfiniFrameApplication.CreateBuilder().UseBlazorWebView(config => config.ConfigureWindow(wb => {
wb.SetTrustAllOrigins(true);
-});
+})).Build();
```
### Host an ASP.NET Core web app
@@ -90,11 +91,14 @@ var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
Install: `dotnet add package InfiniLore.InfiniFrame.WebServer`
```csharp
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
-var app = InfiniFrameWebApplication.CreateBuilder(args)
- .Build()
- .UseAutoServerClose();
+var app = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow("web", static window => window.SetTitle("InfiniFrame"))
+ .UseWebServer("web", web => web.ConfigureWebApplication(application =>
+ application.MapGet("/", () => "Hello from InfiniFrame")))
+ .Build();
app.Run();
```
diff --git a/docs/docs/cpp/native-cpp-api.md b/docs/docs/cpp/native-cpp-api.md
index 39da0870b..822225094 100644
--- a/docs/docs/cpp/native-cpp-api.md
+++ b/docs/docs/cpp/native-cpp-api.md
@@ -1,13 +1,13 @@
# Native C++ API
-InfiniFrame native API documentation is maintained directly in Docusaurus and sourced from the native headers and comments under `src/InfiniFrame.Native`.
+InfiniFrame native API documentation is maintained directly in Docusaurus and sourced from the native headers and comments under `src/InfiniFrame.NativeBridge/Native`.
## Core headers
- `Core/InfiniFrame.h`: top-level native interop include.
- `Core/InfiniFrameWindow.h`: main native window class and callbacks.
- `Core/InfiniFrameDialog.h`: dialog surface for file/folder/message dialogs.
-- `Core/InfiniFrameInitParams.h`: build-time and startup window parameters.
+- `Core/InfiniFrameWindowInitParams.h`: build-time and startup window parameters.
- `Core/InfiniFrameWindowImpl.h`: shared implementation state for platform windows.
## Shared native types
diff --git a/docs/docs/csharp/async-window-contract-design.md b/docs/docs/csharp/async-window-contract-design.md
index fa11abd4a..5b21d3342 100644
--- a/docs/docs/csharp/async-window-contract-design.md
+++ b/docs/docs/csharp/async-window-contract-design.md
@@ -147,7 +147,7 @@ Do not add async getters or async versions of immediate setters. After the prior
## Native ABI additions
-All current exports and the size-equality-checked `InfiniFrameInitParams` layout remain unchanged. Additive operations use an opaque managed context, a 64-bit operation ID, and exactly-once completion callbacks. General operations report a terminal result, native code, and a borrowed UTF-8 failure string; file-dialog callbacks report borrowed native strings for the callback duration:
+All current exports and the size-equality-checked `InfiniFrameWindowInitParams` layout remain unchanged. Additive operations use an opaque managed context, a 64-bit operation ID, and exactly-once completion callbacks. General operations report a terminal result, native code, and a borrowed UTF-8 failure string; file-dialog callbacks report borrowed native strings for the callback duration:
```cpp
using OperationCompletedCallback =
diff --git a/docs/docs/guides/blazor-webview.md b/docs/docs/guides/blazor-webview.md
index 789914a64..97f8760f4 100644
--- a/docs/docs/guides/blazor-webview.md
+++ b/docs/docs/guides/blazor-webview.md
@@ -1,275 +1,57 @@
# Blazor WebView Guide
-`InfiniLore.InfiniFrame.BlazorWebView` integrates a full Blazor WebAssembly-style application into a native window with no HTTP server required. The Blazor runtime runs entirely in-process.
+`InfiniLore.InfiniFrame.BlazorWebView` hosts a Blazor application in an InfiniFrame native window.
-## Contents
-
-- [How It Works](#how-it-works)
-- [Project Setup](#project-setup)
-- [Program.cs](#programcs)
-- [Available Builder API](#available-builder-api)
-- [Dependency Injection](#dependency-injection)
-- [Custom File Provider](#custom-file-provider)
-- [External JS Modules and Trusted Origins](#external-js-modules-and-trusted-origins)
-- [Error Handling](#error-handling)
-- [HttpClient](#httpclient)
-- [Lifecycle](#lifecycle)
-- [Custom Window Chrome](#custom-window-chrome)
-
-## How It Works
-
-InfiniFrame serves Blazor resources from an internal origin (`app://localhost/`) and handles requests inside the native host. Blazor component files, JavaScript, and CSS are served from an `IFileProvider` backed by your `wwwroot/` folder.
-There is no external ASP.NET server required; all communication happens through the native browser bridge.
-
-`app://localhost/` follows normal browser URL semantics. Query strings and fragments remain in the browser-visible URL, while InfiniFrame removes them only when looking up an embedded resource. For example, navigating to `app://localhost/index.html?mode=desktop#settings` serves `index.html`, leaves the full URL visible, and exposes `#settings` through `window.location.hash`.
-
-Same-origin browser requests to application assets are supported with both `fetch()` and `XMLHttpRequest`. The native engines register `app` as a secure, authority-bearing scheme and allow the `app://localhost` origin. Cross-origin access is not enabled implicitly; add trusted origins through the URI security policy only when the application genuinely needs them.
-
-Platform notes:
-- Windows uses WebView2 and requires custom-scheme registration support (`ICoreWebView2EnvironmentOptions4`) to allow top-level `app://localhost/...` navigation.
-- Linux and macOS use WebKit-based engines and do not depend on WebView2.
-- On Windows, if the WebView2 runtime does not support custom-scheme registration, startup fails fast with a clear error asking for a WebView2 runtime update.
-
-## Project Setup
-
-Your project must use the Razor SDK:
-
-```xml
-
-
- net9.0
- Exe
-
-
-
-
-
-
-```
-
-### wwwroot/index.html
-
-A minimal host page:
-
-```html
-
-
-
-
-
-
-
-
-
- Loading...
-
- An unhandled error has occurred.
-
-
-
-
-```
-
-## Program.cs
+## Setup
```csharp
+using InfiniFrame.Application;
using InfiniFrame.BlazorWebView;
-using Microsoft.Extensions.DependencyInjection;
-
-var builder = InfiniFrameBlazorAppBuilder.CreateDefault(args, w => w
- .SetTitle("My Blazor App")
- .SetSize(1280, 720)
- .Center()
- .SetChromeless(true) // Optional: remove native title bar
-);
-
-// Register services (same as a standard Blazor or ASP.NET Core app)
-builder.Services.AddSingleton();
-builder.Services.AddScoped();
-
-// Register root components (these map to elements in index.html)
-builder.RootComponents.Add("#app");
-builder.RootComponents.Add("head::after");
-
-builder.Build().Run();
-```
-
-`Run()` blocks until the window is closed and then disposes all services.
-
-## Available Builder API
-
-`InfiniFrameBlazorAppBuilder` exposes three properties for configuration:
-
-| Property | Type | Description |
-|------------------|-----------------------------|------------------------------------------------------------------------------|
-| `WindowBuilder` | `IInfiniFrameWindowBuilder` | Fluent window configuration; all options from the generated C# API reference |
-| `Services` | `IServiceCollection` | Standard .NET DI container |
-| `RootComponents` | `RootComponentList` | Maps Blazor components to CSS selectors in index.html |
-
-### Configuring the window separately
-
-```csharp
-var builder = InfiniFrameBlazorAppBuilder.CreateDefault();
-
-builder.WithInfiniFrameWindowBuilder(w => w
- .SetTitle("Configured Later")
- .SetDevToolsEnabled(true)
-);
-```
-
-## Dependency Injection
-
-The following services are automatically registered and available for injection:
-
-| Service | Lifetime | Description |
-|----------------------|-----------|---------------------------------------|
-| `IInfiniFrameWindow` | Singleton | The native window instance |
-| `IInfiniFrameJs` | Scoped | JavaScript interop utilities |
-| `HttpClient` | Scoped | Preconfigured for in-process requests |
-| `Dispatcher` | Singleton | Blazor's component dispatcher |
-
-### Injecting the window in a component
-
-```razor
-@inject IInfiniFrameWindow Window
-
-
-
-
-@code {
- void Minimize() => Window.Invoke(() => { /* window ops must be on UI thread */ });
- void Close() => Window.Close();
-}
-```
-
-## Custom File Provider
-
-By default, files are served from `{AppBaseDirectory}/wwwroot/`.
-You can supply a custom `IFileProvider` for embedded resources, encrypted assets, or virtual file systems:
-
-```csharp
-using Microsoft.Extensions.FileProviders;
-var embeddedProvider = new EmbeddedFileProvider(typeof(Program).Assembly, "MyApp.wwwroot");
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => window
+ .SetTitle("My Blazor App")
+ .SetSize(1280, 720));
-var builder = InfiniFrameBlazorAppBuilder.CreateDefault(
- fileProvider: embeddedProvider,
- args: args
-);
-```
-
-## External JS Modules and Trusted Origins
-
-If your app imports scripts from external origins (for example `import ... from "https://cdn.example/..."`), keep `WebSecurity` enabled and explicitly trust those origins.
-
-```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
- wb.AddTrustedOrigin("https://xyz");
- // add redirects too if needed (e.g. cdn.jsdelivr.net, unpkg.com, etc.)
+builder.UseBlazorWebView(configuration => {
+ configuration.RootComponents.Add("app");
});
-```
-
-For multiple hosts:
-```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
- wb.AddTrustedOrigin("https://xyz");
- wb.AddTrustedOrigin("https://cdn.jsdelivr.net");
- wb.AddTrustedOrigin("https://unpkg.com");
-});
+await using InfiniFrameApplication app = builder.Build();
+await app.RunAsync();
```
-To disable origin checks entirely (not recommended for production), opt in explicitly:
+Configure window features before registering the integration with `WithWindow` or `WithWindow(id, ...)`.
+`ConfigureWindow` remains available for integration-specific configuration.
```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
- wb.SetTrustAllOrigins(true);
+builder.WithWindow(window => window.SetChromeless(true));
+builder.UseBlazorWebView(configuration => {
+ configuration.ConfigureWindow(window => window.SetDevToolsEnabled(true));
+ configuration.RootComponents.Add("app");
});
```
-Do not use `.SetWebSecurityEnabled(false)` as a workaround for this scenario.
+The current manager supports one target window per Blazor WebView integration. Use separate application integrations when independent window managers are required.
-## Error Handling
+## Services And Components
-Unhandled exceptions in the process are caught automatically and shown as a native message dialog:
-
-```
-Fatal exception
-System.NullReferenceException: Object reference not set...
-```
-
-To customize error handling, register a handler before `Build()`:
+Register application services on `builder.Services` and root components on the integration configuration:
```csharp
-AppDomain.CurrentDomain.UnhandledException += (_, e) => {
- // Custom logging or reporting
-};
+builder.Services.AddSingleton();
+builder.UseBlazorWebView(configuration => {
+ configuration.RootComponents.Add("app");
+ configuration.RootComponents.Add("head::after");
+});
```
-## HttpClient
-
-An `HttpClient` is registered automatically with `BaseAddress` set to the internal app base URI.
-This lets you make in-process requests to your static assets or call external APIs:
+`IInfiniFrameWindow`, `IInfiniFrameJs`, `HttpClient`, and the Blazor dispatcher are registered by the integration where applicable.
-```razor
-@inject HttpClient Http
+## File Providers And Security
-@code {
- protected override async Task OnInitializedAsync() {
- var data = await Http.GetFromJsonAsync("data/mydata.json");
- }
-}
-```
+The integration serves static assets from the application `wwwroot` and generated static-web-assets locations. Use the configuration APIs for custom file providers and explicitly trust external origins when needed. Keep WebSecurity enabled unless disabling it is an intentional development-only decision.
## Lifecycle
-```
-InfiniFrameBlazorAppBuilder.CreateDefault()
- ↓
-Configure Services & RootComponents
- ↓
-.Build() ← Registers the custom scheme, creates the window
- ↓
-.Run() ← Starts the Blazor runtime, blocks until window closes
- ↓
-DisposeAsync() ← Disposes all services
-```
-
-## Debugging workflow
-
-Use devtools and remote debugging separately:
-
-```csharp
-var builder = InfiniFrameBlazorAppBuilder.CreateDefault(args, w => w
- .SetDevToolsEnabled(true)
- .SetWebInspectorEnabled(true) // macOS 13.3+ Safari Web Inspector attachability
- .SetRemoteDebuggingPort(9222) // Windows and Linux, startup-only
-);
-
-var app = builder.Build();
-
-if (app.Window.Debug.TryGetRemoteDebuggingEndpoint(out Uri? endpoint))
- Console.WriteLine($"Remote debug endpoint: {endpoint}");
-
-app.Run();
-```
-
-- `SetDevToolsEnabled(true)` controls local inspector UI.
-- `SetWebInspectorEnabled(true)` controls WKWebView Safari Web Inspector attachability on macOS 13.3+.
-- `SetRemoteDebuggingPort(int? port)` controls TCP endpoint availability (`1..65535`, `0/null` disables).
-- Linux inspector endpoint uses WebKitGTK inspector server (`http://127.0.0.1:/`).
-- On Linux, WebKit requires developer extras for remote inspector and keeps them enabled while remote debugging is active.
-- On Linux, inspector server configuration is process-scoped (shared across windows in the same process).
-- On unsupported platforms (macOS), enabling remote debugging throws `PlatformNotSupportedException`.
-- On unsupported platforms (Windows/Linux, or macOS below 13.3), enabling web inspector mode throws `PlatformNotSupportedException`.
-
-## Custom Window Chrome
-
-Combine with `InfiniLore.InfiniFrame.Blazor` for a fully custom title bar.
-
-See the [Custom Window Chrome Guide](custom-window-chrome.md) for details. For JavaScript interop and built-in message handlers, see the [JavaScript Interop Guide](javascript-interop.md).
-
-## Examples
-
-- `InfiniFrameExample.BlazorWebView` (`examples/InfiniFrameExample.BlazorWebView`) - minimal Blazor app with window configuration and Serilog
-- `InfiniFrameExample.BlazorWebView.MultiWindowSample` (`examples/InfiniFrameExample.BlazorWebView.MultiWindowSample`) - multiple windows each hosting a different Blazor component
+`Run` or `RunAsync` starts the native application and the Blazor WebView. Closing the application disposes the WebView manager, message pump, windows, and registered services.
diff --git a/docs/docs/guides/core-window.md b/docs/docs/guides/core-window.md
index 239d47e54..2c4436a6a 100644
--- a/docs/docs/guides/core-window.md
+++ b/docs/docs/guides/core-window.md
@@ -33,7 +33,7 @@ The returned `IInfiniFrameWindow` gives you full control over the window at runt
## Single-File Native Packaging
-When your app is published as a single-file executable with embedded InfiniFrame native binaries, call `InfiniFrameSingleFileBootstrap.Initialize()` before creating any windows.
+When your app is published as a single-file executable with embedded InfiniFrame native binaries, call `InfiniFrameSingleFile.Initialize()` before creating any windows.
```csharp
using InfiniFrame;
@@ -41,7 +41,7 @@ using InfiniFrame;
public static class Program {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameSingleFileBootstrap.Initialize();
+ InfiniFrameSingleFile.Initialize();
var window = InfiniFrameWindowBuilder.Create()
.SetTitle("My App")
diff --git a/docs/docs/guides/getting-started.md b/docs/docs/guides/getting-started.md
index b58ce4ce2..4655d6677 100644
--- a/docs/docs/guides/getting-started.md
+++ b/docs/docs/guides/getting-started.md
@@ -74,7 +74,7 @@ using InfiniFrame;
public static class Program {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameSingleFileBootstrap.Initialize();
+ InfiniFrameSingleFile.Initialize();
var window = InfiniFrameWindowBuilder.Create()
.SetTitle("Hello, InfiniFrame")
@@ -124,21 +124,24 @@ A minimal `index.html`:
### Program.cs
```csharp
+using InfiniFrame.Application;
using InfiniFrame.BlazorWebView;
using Microsoft.Extensions.DependencyInjection;
-var builder = InfiniFrameBlazorAppBuilder.CreateDefault(args, w => w
- .SetTitle("My Blazor App")
- .SetSize(1280, 720)
- .Center()
-);
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => window
+ .SetTitle("My Blazor App")
+ .SetSize(1280, 720)
+ .Center());
// Register your services
builder.Services.AddSingleton();
// Register root Blazor components
-builder.RootComponents.Add("#app");
-builder.RootComponents.Add("head::after");
+builder.UseBlazorWebView(configuration => {
+ configuration.RootComponents.Add("app");
+ configuration.RootComponents.Add("head::after");
+});
builder.Build().Run();
```
@@ -166,21 +169,19 @@ dotnet add package InfiniLore.InfiniFrame.WebServer
### Program.cs
```csharp
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
-var app = InfiniFrameWebApplication.CreateBuilder(args)
- .Build()
- .UseAutoServerClose();
-
-// Configure the ASP.NET Core pipeline on app.WebApp
-app.WebApp.UseRouting();
-app.WebApp.MapGet("/", () => "Hello from InfiniFrame");
+var app = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow("web", static _ => { })
+ .UseWebServer("web", web => web.ConfigureWebApplication(application =>
+ application.MapGet("/", () => "Hello from InfiniFrame")))
+ .Build();
app.Run();
```
-The start URL is automatically read from `ASPNETCORE_URLS` or the `urls` configuration key.
-`UseAutoServerClose()` ensures the server shuts down gracefully when the window is closed.
+The start URL is resolved from the server's bound address after startup, and the server shuts down with the InfiniFrame application.
## Next Steps
diff --git a/docs/docs/guides/pack-tool.md b/docs/docs/guides/pack-tool.md
index 2fe4ef038..844c30095 100644
--- a/docs/docs/guides/pack-tool.md
+++ b/docs/docs/guides/pack-tool.md
@@ -30,7 +30,7 @@ Compared to a regular `dotnet publish`, the target additionally:
- Removes unpacked sidecar files from the final publish directory
- Performs a two-pass publish to ensure all content is available before embedding
-Because native files are embedded as resources, your app must initialize the runtime resolver at startup with `InfiniFrameSingleFileBootstrap.Initialize()`.
+Because native files are embedded as resources, your app must initialize the runtime resolver at startup with `InfiniFrameSingleFile.Initialize()`.
## How It Works
@@ -147,7 +147,7 @@ using InfiniFrame;
public static class Program {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameSingleFileBootstrap.Initialize();
+ InfiniFrameSingleFile.Initialize();
var window = InfiniFrameWindowBuilder.Create()
.SetTitle("My App")
@@ -163,7 +163,7 @@ public static class Program {
Why this is required:
- The publish target embeds `InfiniFrame.Native` and platform loader files (`WebView2Loader.dll` on Windows) as resources.
-- `InfiniFrameSingleFileBootstrap.Initialize()` extracts them to a temporary RID-specific folder and registers a native resolver so P/Invoke can load them.
+- `InfiniFrameSingleFile.Initialize()` extracts them to a temporary RID-specific folder and registers a native resolver so P/Invoke can load them.
Alternatively, use the higher-level `InfiniFrameSingleFile.Initialize()` helper which also configures embedded static web assets for Blazor apps:
diff --git a/docs/docs/guides/trim-aot-compatibility.md b/docs/docs/guides/trim-aot-compatibility.md
index 4ec08e727..abf018a84 100644
--- a/docs/docs/guides/trim-aot-compatibility.md
+++ b/docs/docs/guides/trim-aot-compatibility.md
@@ -6,9 +6,10 @@ InfiniFrame includes CI validation lanes for trimming and NativeAOT compatibilit
- The public APIs that rely on runtime reflection or dynamic code generation are explicitly annotated with `RequiresUnreferencedCode` and/or `RequiresDynamicCode`.
- Trim/AOT compatibility checks run in CI and must pass before release workflows continue.
-- `InfiniFrame.SingleFile` is validated with a NativeAOT smoke publish using:
+- The core `InfiniFrame` package is validated with a NativeAOT smoke publish using:
- `PublishTrimmed=true`
- `PublishAot=true`
+- The `InfiniFrame.SingleFile` package and MSBuild targets are validated separately by package/target tests; they are not included in the consumer smoke publish.
## Consumer Guidance
@@ -19,4 +20,4 @@ InfiniFrame includes CI validation lanes for trimming and NativeAOT compatibilit
dotnet publish -c Release -r -p:PublishTrimmed=true -p:PublishAot=true
```
-- If your app uses framework features that depend on reflection (configuration binding, runtime component activation, etc.), account for their requirements explicitly in your trimming strategy.
\ No newline at end of file
+- If your app uses framework features that depend on reflection (configuration binding, runtime component activation, etc.), account for their requirements explicitly in your trimming strategy.
diff --git a/docs/docs/guides/web-server.md b/docs/docs/guides/web-server.md
index a8917b614..ca7c64481 100644
--- a/docs/docs/guides/web-server.md
+++ b/docs/docs/guides/web-server.md
@@ -1,28 +1,6 @@
# Web Server Guide
-`InfiniLore.InfiniFrame.WebServer` runs a standard ASP.NET Core web application in a background thread while opening a native window pointed at it.
-This approach gives you the full ASP.NET Core pipeline (middleware, controllers, SignalR, minimal APIs, Blazor Server) without any browser overhead.
-
-For cross-thread dispatch from ASP.NET Core to the window thread, see the [Invoke feature](invoke-feature.md). For an overview of the feature system, see [Window Features Architecture](window-features-architecture.md).
-
-## Contents
-
-- [How It Works](#how-it-works)
-- [Installation](#installation)
-- [Minimal Setup](#minimal-setup)
-- [Builder API](#builder-api)
-- [Start URL](#start-url)
-- [Accessing the Window from ASP.NET Core](#accessing-the-window-from-aspnet-core)
-- [Graceful Shutdown](#graceful-shutdown)
-- [Example: Blazor Server](#example-blazor-server)
-- [Static Web Assets](#static-web-assets)
-- [Thread Model](#thread-model)
-
-## How It Works
-
-- The ASP.NET Core server starts on a background thread.
-- A native window opens and navigates to the server's URL.
-- Both shut down together when the window is closed (with `UseAutoServerClose()`).
+`InfiniLore.InfiniFrame.WebServer` hosts an ASP.NET Core application alongside native InfiniFrame windows.
## Installation
@@ -33,168 +11,71 @@ dotnet add package InfiniLore.InfiniFrame.WebServer
## Minimal Setup
```csharp
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
-var app = InfiniFrameWebApplication.CreateBuilder(args)
- .Build()
- .UseAutoServerClose();
-
-app.WebApp.MapGet("/", () => "Hello from InfiniFrame");
+var app = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow("web", window => window.SetTitle("My Desktop App"))
+ .UseWebServer("web", web => web.ConfigureWebApplication(application =>
+ application.MapGet("/", () => "Hello from InfiniFrame")))
+ .Build();
app.Run();
```
-`app.Run()` starts the web server in the background, opens the window, and blocks until the window is closed.
-
-## Builder API
+`Run()` starts the server, opens the window, and stops the server during application shutdown.
-`InfiniFrameWebApplication.CreateBuilder(args)` returns an `InfiniFrameWebApplicationBuilder` with two properties:
+## Configuration
-| Property | Type | Description |
-|----------|----------------------------|----------------------------------------------------------------------|
-| `WebApp` | `WebApplicationBuilder` | Standard ASP.NET Core builder; add services, configure Kestrel, etc. |
-| `Window` | `InfiniFrameWindowBuilder` | Fluent window configuration |
-
-### Configuring the window
+`InfiniFrameWebServerConfiguration` exposes `WebHost` for Kestrel configuration and
+`ConfigureWebApplication` for configuring the built `WebApplication`:
```csharp
-var builder = InfiniFrameWebApplication.CreateBuilder(args);
-
-builder.Window
- .SetTitle("My Desktop App")
- .SetSize(1280, 720)
- .Center()
- .SetDevToolsEnabled(true)
- .SetRemoteDebuggingPort(9222); // Windows and Linux
-
-builder.WebApp.Services.AddControllers();
-builder.WebApp.Services.AddSignalR();
-
-var app = builder.Build().UseAutoServerClose();
-app.WebApp.MapControllers();
-app.WebApp.MapHub("/hub");
-
-app.Run();
-```
-
-Remote debugging notes:
-- `SetRemoteDebuggingPort(...)` is startup-only and validates `1..65535` (`0/null` disables).
-- Linux and Windows are supported. On unsupported platforms (macOS), enabling it throws `PlatformNotSupportedException`.
-- Use `app.Window.Debug.TryGetRemoteDebuggingEndpoint(out Uri? endpoint)` after startup to retrieve the endpoint when available.
-
-## Start URL
-
-The window's start URL is automatically resolved from configuration in this priority order:
-
-1. `ASPNETCORE_URLS` environment variable
-2. `urls` configuration key (e.g. in `appsettings.json`)
-3. Manual override via `builder.Window.SetStartPageUrl(...)`
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow("web", window => window
+ .SetTitle("Web App")
+ .SetSize(1280, 720));
+
+builder.UseWebServer("web", web => {
+ web.WebHost.UseUrls("http://127.0.0.1:5055");
+ web.ConfigureWebApplication(application => {
+ application.UseRouting();
+ application.MapGet("/health", () => Results.Ok());
+ });
+});
-```json
-{
- "urls": "http://localhost:5200"
-}
+builder.Build().Run();
```
-If multiple URLs are configured (e.g. `"http://localhost:5200;https://localhost:7200"`), the first one is used as the window's start URL.
+The server's bound address is resolved after startup. Wildcard bindings are converted to a loopback URL before navigation and origin trust are configured.
-## Accessing the Window from ASP.NET Core
+## Dependency Injection
-`IInfiniFrameWindow` and `IInfiniFrameWindowBuilder` are registered in the web app's DI container:
+Application services are copied into the ASP.NET Core service collection. Window APIs can therefore be injected into handlers:
```csharp
-app.WebApp.MapGet("/close", (IInfiniFrameWindow window) => {
+application.MapGet("/close", (IInfiniFrameWindow window) => {
window.Close();
return Results.Ok();
});
```
-```csharp
-public class MyController(IInfiniFrameWindow window) : ControllerBase {
- [HttpGet("minimize")]
- public IActionResult Minimize() {
- window.Invoke(() => { /* minimize logic */ });
- return Ok();
- }
-}
-```
-
-> **Note:** Window operations that affect the native UI must be marshalled to the window thread using `window.Invoke(...)`.
-
-## Graceful Shutdown
-
-### UseAutoServerClose
-
-Automatically stops the web server when the window is closed or a close is requested:
-
-```csharp
-var app = builder.Build().UseAutoServerClose();
-```
-
-Internally this registers handlers on both `WindowClosing` and `WindowClosingRequested` that call `WebApp.StopAsync()` in a background task, so the UI thread is never blocked.
-
-### Manual shutdown
-
-```csharp
-app.Stop(); // Stops the web server and closes the window
-```
-
-```csharp
-await app.WebApp.StopAsync(); // Stop server only
-app.Window.Close(); // Then close window
-```
-
-## Example: Blazor Server
+## Blazor Server
```csharp
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
-var builder = InfiniFrameWebApplication.CreateBuilder(args);
-
-builder.Window
- .SetTitle("Blazor Server App")
- .SetSize(1280, 720)
- .Center();
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => window.SetTitle("Blazor Server"));
-builder.WebApp.Services.AddRazorComponents()
+builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
-var app = builder.Build().UseAutoServerClose();
+builder.UseWebServer(web => web.ConfigureWebApplication(application =>
+ application.MapRazorComponents().AddInteractiveServerRenderMode()));
-app.WebApp.UseStaticFiles();
-app.WebApp.UseAntiforgery();
-app.WebApp.MapRazorComponents()
- .AddInteractiveServerRenderMode();
-
-app.Run();
+builder.Build().Run();
```
-## Static Web Assets
-
-`UseStaticWebAssets()` is called automatically during builder initialization, so static files from Razor class libraries are served correctly without additional configuration.
-
-`UseDefaultFiles()` is also applied during `Build()`, which causes requests to `/` to serve `wwwroot/index.html` if it exists.
-
-## Thread Model
-
-| Thread | Runs |
-|-------------------|---------------------------|
-| Main thread | Native window (UI thread) |
-| Background thread | ASP.NET Core / Kestrel |
-
-All window API calls from ASP.NET Core handlers must use `window.Invoke(...)` to marshal to the window thread.
-Web server calls from window event handlers can be made directly since ASP.NET Core is thread-safe.
-
-> **Windows:** The main thread must be STA. Add `[STAThread]` to your `Main` method and use an explicit `static void Main()`. Top-level statements and `async Task Main` do not support STA correctly.
-
-## Examples
-
-- `InfiniFrameExample.WebApp.Blazor` (`examples/InfiniFrameExample.WebApp.Blazor`) - Blazor Server with InteractiveServerComponents, HttpClient factory, and InfiniFrameJs
-- `InfiniFrameExample.WebApp.React` (`examples/InfiniFrameExample.WebApp.React`) - React frontend with custom scheme handler and two-way messaging
-- `InfiniFrameExample.WebApp.Vue` (`examples/InfiniFrameExample.WebApp.Vue`) - Vue.js frontend with all built-in JS message handlers
-
-## See Also
-
-- [Invoke Feature](invoke-feature.md) Cross-thread dispatch to the window's native thread
-- [Window Features Architecture](window-features-architecture.md) How the feature system works
-- [Core Window Guide](core-window.md) Builder API and feature overview
+For static assets, configure the appropriate ASP.NET Core static-file or static-asset middleware in `ConfigureWebApplication`.
diff --git a/docs/docs/guides/window-features-architecture.md b/docs/docs/guides/window-features-architecture.md
index 21beb5e81..9d7535a3d 100644
--- a/docs/docs/guides/window-features-architecture.md
+++ b/docs/docs/guides/window-features-architecture.md
@@ -37,7 +37,7 @@ builder.Features.Size.SetSize(1280, 720); // Direct feature access
builder.SetSize(1280, 720); // Extension method equivalent
```
-Builder features apply their settings to `InfiniFrameNativeParameters`, which the native layer reads during window creation. After `Build()`, builder-only settings cannot be changed.
+Builder features apply their settings to `InfiniFrameNativeWindowParameters`, which the native layer reads during window creation. After `Build()`, builder-only settings cannot be changed.
### Runtime Feature (`IInfiniFrameWindowFeature`)
@@ -134,7 +134,7 @@ builder.Features.InstanceArbitration // IInstanceArbitrationInfiniFrameWindowBui
## How Features Are Wired
-1. **Builder phase**: Each builder feature implements `IInfiniFrameWindowBuilderFeature` with an `ApplyToNativeParameters(ref InfiniFrameNativeParameters)` method. The builder collects all settings into a native parameters struct.
+1. **Builder phase**: Each builder feature implements `IInfiniFrameWindowBuilderFeature` with an `ApplyToNativeParameters(ref InfiniFrameNativeWindowParameters)` method. The builder collects all settings into a native parameters struct.
2. **Build**: `InfiniFrameWindowBuilder.Build()` creates the window, then `InfiniFrameWindowFeaturesFactory` creates all runtime feature instances from the DI container, passing the window and the original builder.
diff --git a/docs/docs/migration/photino-breaking-changes.md b/docs/docs/migration/photino-breaking-changes.md
index 5d3bad0ae..66fd25cc4 100644
--- a/docs/docs/migration/photino-breaking-changes.md
+++ b/docs/docs/migration/photino-breaking-changes.md
@@ -32,7 +32,7 @@ For detailed documentation on the new feature-based API, see:
| C# namespace | `Photino.NET` | `InfiniFrame` |
| Native DLL | `Photino.Native` | `InfiniFrame.Native` (internal) |
| C++ class | `Photino` | `InfiniFrameWindow` |
-| C++ init params | `PhotinoInitParams` | `InfiniFrameInitParams` |
+| C++ init params | `PhotinoInitParams` | `InfiniFrameWindowInitParams` |
| Exported function prefix | `Photino_` | `InfiniFrameNative_` |
| Default window title | `"Photino"` | `"InfiniFrame"` |
| Default user agent | `"Photino WebView"` | `"InfiniFrame WebView"` |
@@ -250,19 +250,22 @@ For BlazorWebView, InfiniFrame serves content from `app://localhost/` and valida
The preferred migration pattern is to keep web security on and explicitly trust only the origins you need:
```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
- wb.AddTrustedOrigin("https://xyz");
- wb.AddTrustedOrigin("https://cdn.jsdelivr.net");
- wb.AddTrustedOrigin("https://unpkg.com");
-});
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => window
+ .AddTrustedOrigin("https://xyz")
+ .AddTrustedOrigin("https://cdn.jsdelivr.net")
+ .AddTrustedOrigin("https://unpkg.com"));
+builder.UseBlazorWebView(configuration => configuration.RootComponents.Add("app"));
+var app = builder.Build();
```
If you need broad compatibility during migration, you can opt in to trusting all origins:
```csharp
-var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
- wb.SetTrustAllOrigins(true);
-});
+var builder = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => window.SetTrustAllOrigins(true));
+builder.UseBlazorWebView(configuration => configuration.RootComponents.Add("app"));
+var app = builder.Build();
```
`SetTrustAllOrigins(true)` is intentionally high-risk and should be treated as a temporary dev-time switch, not a production default.
diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/App.razor b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/App.razor
index dab47188f..e16e6152e 100644
--- a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/App.razor
+++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/App.razor
@@ -1,3 +1,4 @@
+@using global::MudBlazor
diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/Index.razor b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/Index.razor
index 053720acd..666add529 100644
--- a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/Index.razor
+++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Components/Pages/Index.razor
@@ -98,7 +98,7 @@
@if (_eventLog.Count == 0) {
No events yet. Click a button above.
}
- @foreach (var entry in _eventLog) {
+ @foreach (string entry in _eventLog) {
@entry
diff --git a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Program.cs b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Program.cs
index 5b4217353..8106aa4c7 100644
--- a/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Program.cs
+++ b/examples/InfiniFrameExample.BlazorWebView.MudBlazor/Program.cs
@@ -1,14 +1,15 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
+using InfiniFrameExample.BlazorWebView.MudBlazor.Components;
+using Serilog;
+using System.Drawing;
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.BlazorWebView;
-using InfiniFrameExample.BlazorWebView.MudBlazor.Components;
-using MudBlazor.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Serilog;
-using System.Drawing;
+using MudBlazor.Services;
namespace InfiniFrameExample.BlazorWebView.MudBlazor;
// ---------------------------------------------------------------------------------------------------------------------
@@ -25,9 +26,12 @@ private static void Main(string[] args) {
try {
Log.Information("Starting InfiniFrame BlazorWebView MudBlazor example...");
- var appBuilder = InfiniFrameBlazorAppBuilder.CreateDefault(args);
-
- appBuilder.Services
+ InfiniFrameApplicationBuilder builder = InfiniFrameApplication.CreateBuilder(args);
+ builder.WithWindow(window => window
+ .SetIconFile("wwwroot/favicon.ico")
+ .SetLocation(new Point(100, 100))
+ .SetSize(new Size(800, 600)));
+ builder.Services
.AddLogging(config => {
config.ClearProviders();
config.AddSerilog();
@@ -38,17 +42,11 @@ private static void Main(string[] args) {
})
.AddMudServices();
- appBuilder.RootComponents.Add("app");
-
- appBuilder.WithInfiniFrameWindowBuilder(builder => {
- builder
- .SetIconFile("wwwroot/favicon.ico")
- .SetLocation(new Point(100, 100))
- .SetSize(new Size(800, 600));
- });
-
- Log.Information("Building InfiniFrame application...");
- InfiniFrameBlazorApp app = appBuilder.Build();
+ InfiniFrameApplication app = builder
+ .UseBlazorWebView(configuration => {
+ configuration.RootComponents.Add("app");
+ })
+ .Build();
Log.Information("Running application...");
app.Run();
diff --git a/examples/InfiniFrameExample.BlazorWebView/Program.cs b/examples/InfiniFrameExample.BlazorWebView/Program.cs
index 6fb0fd100..b619ca4a7 100644
--- a/examples/InfiniFrameExample.BlazorWebView/Program.cs
+++ b/examples/InfiniFrameExample.BlazorWebView/Program.cs
@@ -1,13 +1,14 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
+using System.Drawing;
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.BlazorWebView;
using InfiniFrameExample.BlazorWebView.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
-using System.Drawing;
namespace InfiniFrameExample.BlazorWebView;
// ---------------------------------------------------------------------------------------------------------------------
@@ -16,41 +17,32 @@ namespace InfiniFrameExample.BlazorWebView;
public static class Program {
[STAThread]
private static void Main(string[] args) {
- var appBuilder = InfiniFrameBlazorAppBuilder.CreateDefault(args);
+ InfiniFrameApplicationBuilder builder = InfiniFrameApplication.CreateBuilder(args);
- appBuilder.Services.AddLogging(config => {
- config.ClearProviders();
- config.AddSerilog();
+ builder.Services.AddLogging(config => {
+ config.ClearProviders();
+ config.AddSerilog();
});
- appBuilder.Services.AddSerilog(config => {
- config.WriteTo.Async(static c => c.Console())
- .MinimumLevel.Debug();
+ builder.Services.AddSerilog(config => {
+ config.WriteTo.Async(static c => c.Console())
+ .MinimumLevel.Debug();
});
- // register the root component and selector
- appBuilder.RootComponents.Add("app");
-
- appBuilder.WithInfiniFrameWindowBuilder(builder => {
- builder
- // .SetTransparent(true)
- // .SetChromeless(true)
- // .SetResizable(true)
- .SetIconFile("wwwroot/favicon.ico")
- .SetWindowsAppUserModelId("InfiniLore.InfiniFrameExample.BlazorWebView")
- // .Center()
- // .SetUseOsDefaultSize(true)
- // .SetUseOsDefaultLocation(true);
- // .SetTitle("InfiniLore InfiniFrame.Blazor Sample")
- .SetLocation(new Point(100, 100))
- .SetSize(new Size(800, 600))
- // .SetMaxSize(new Size(800, 600))
- // .SetMinSize(new Size(600, 400))
- ;
+ builder.WithWindow(window => window
+ .SetIconFile("wwwroot/favicon.ico")
+ .SetLocation(new Point(100, 100))
+ .SetSize(new Size(800, 600)));
+
+
+ builder.UseBlazorWebView(configuration => {
+ // register the root component and selector
+ configuration.RootComponents.Add("app");
+
});
- InfiniFrameBlazorApp app = appBuilder.Build();
+ InfiniFrameApplication app = builder.Build();
app.Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/InfiniFrameExample.BlazorWebView/README.md b/examples/InfiniFrameExample.BlazorWebView/README.md
index f6e4249ca..2236ee026 100644
--- a/examples/InfiniFrameExample.BlazorWebView/README.md
+++ b/examples/InfiniFrameExample.BlazorWebView/README.md
@@ -5,7 +5,7 @@ Demonstrates the minimal setup for hosting a Blazor application inside a native
## What it shows
-- `InfiniFrameBlazorAppBuilder.CreateDefault()` entry point
+- `InfiniFrameApplication.CreateBuilder()` entry point
- Registering a root Blazor component (``) mapped to the `#app` selector
- Configuring the window: size, position, and icon file
- Integrating Serilog for structured logging via `Microsoft.Extensions.Logging`
@@ -20,15 +20,17 @@ dotnet run --project examples/InfiniFrameExample.BlazorWebView
## Key code
```csharp
-var appBuilder = InfiniFrameBlazorAppBuilder.CreateDefault(args);
+var appBuilder = InfiniFrameApplication.CreateBuilder(args);
-appBuilder.RootComponents.Add("app");
+appBuilder.UseBlazorWebView(config => {
+ config.RootComponents.Add("app");
-appBuilder.WithInfiniFrameWindowBuilder(builder => builder
+ config.ConfigureWindow(builder => builder
.SetIconFile("favicon.ico")
.SetLocation(new Point(100, 100))
.SetSize(new Size(800, 600))
-);
+ );
+});
appBuilder.Build().Run();
```
diff --git a/examples/InfiniFrameExample.NativeMenu/Program.cs b/examples/InfiniFrameExample.NativeMenu/Program.cs
index e7812b714..3185dfc4b 100644
--- a/examples/InfiniFrameExample.NativeMenu/Program.cs
+++ b/examples/InfiniFrameExample.NativeMenu/Program.cs
@@ -4,6 +4,7 @@
using InfiniFrame;
using System.Drawing;
using System.Text.Json;
+using InfiniFrame.Application;
namespace InfiniFrameExample.NativeMenu;
// ---------------------------------------------------------------------------------------------------------------------
@@ -71,18 +72,19 @@ public static void Main(string[] args) {
]
);
- IInfiniFrameWindow window = InfiniFrameWindowBuilder.Create()
- .SetTitle("InfiniFrame Native Menu Example")
- .SetSize(new Size(960, 640))
- .CenteredOnMainMonitor()
- .SetMenuBar(menuBar)
- .UseEmbeddedWwwrootAssets(
- scheme: "app",
- includePhysicalFallback: true,
- physicalWwwrootPath: Path.Join(AppContext.BaseDirectory, "wwwroot"),
- setStartUrl: true
- )
- .RegisterWebMessageReceivedHandler((win, message) => {
+ InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(builder => builder
+ .SetTitle("InfiniFrame Native Menu Example")
+ .SetSize(new Size(960, 640))
+ .CenteredOnMainMonitor()
+ .SetMenuBar(menuBar)
+ .UseEmbeddedWwwrootAssets(
+ scheme: "app",
+ includePhysicalFallback: true,
+ physicalWwwrootPath: Path.Join(AppContext.BaseDirectory, "wwwroot"),
+ setStartUrl: true
+ )
+ .RegisterWebMessageReceivedHandler((win, message) => {
string? action = ExtractAction(message);
if (action == null) return;
@@ -109,10 +111,9 @@ public static void Main(string[] args) {
win.SendWebMessage($"status:Action: {action}");
break;
}
- })
- .Build();
-
- window.WaitForClose();
+ }))
+ .Build()
+ .Run();
}
private static string? ExtractAction(string rawMessage) {
diff --git a/examples/InfiniFrameExample.TrimAotSmoke/Program.cs b/examples/InfiniFrameExample.TrimAotSmoke/Program.cs
index 9e413fde6..e3eb57632 100644
--- a/examples/InfiniFrameExample.TrimAotSmoke/Program.cs
+++ b/examples/InfiniFrameExample.TrimAotSmoke/Program.cs
@@ -2,6 +2,7 @@
// Imports
// ---------------------------------------------------------------------------------------------------------------------
using InfiniFrame;
+using InfiniFrame.Application;
namespace InfiniFrameExample.TrimAotSmoke;
@@ -11,13 +12,13 @@ namespace InfiniFrameExample.TrimAotSmoke;
public static class Program {
[STAThread]
public static void Main() {
- IInfiniFrameWindow window = InfiniFrameWindowBuilder.Create()
- .SetTitle("InfiniFrame Trim/AOT Smoke")
- .SetSize(800, 600)
- .CenteredOnMainMonitor()
- .UseEmbeddedWwwrootAssets()
- .Build();
-
- window.WaitForClose();
+ InfiniFrameApplication.CreateBuilder()
+ .WithWindow(builder => builder
+ .SetTitle("InfiniFrame Trim/AOT Smoke")
+ .SetSize(800, 600)
+ .CenteredOnMainMonitor()
+ .UseEmbeddedWwwrootAssets())
+ .Build()
+ .Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/README.md b/examples/README.md
index 9fe382eb2..fa78dee05 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -24,7 +24,7 @@ All examples require the native `InfiniFrame.Native` library to be built first
```bash
# From the repo root build the native library for your platform
-cmake -S src/InfiniFrame.Native -B artifacts/native/windows/x64/Debug -DCMAKE_BUILD_TYPE=Debug
+cmake -S src/InfiniFrame.NativeBridge/Native -B artifacts/native/windows/x64/Debug -DCMAKE_BUILD_TYPE=Debug
cmake --build artifacts/native/windows/x64/Debug
# Then run an example
diff --git a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Components/App.razor b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Components/App.razor
index bc8e178c9..021aae981 100644
--- a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Components/App.razor
+++ b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Components/App.razor
@@ -1,4 +1,5 @@
-
+@using global::MudBlazor
+
diff --git a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Program.cs b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Program.cs
index 09b29ccad..898f5dae3 100644
--- a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Program.cs
+++ b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.MudBlazor/Program.cs
@@ -2,8 +2,10 @@
// Imports
// ---------------------------------------------------------------------------------------------------------------------
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.BlazorWebView;
using InfiniFrame.SingleFile;
+using InfiniFrame.Window.Features.WebMessaging.Handlers;
using InfiniFrameExample.SingleFileExe.MudBlazor.Components;
using MudBlazor.Services;
using Serilog;
@@ -25,9 +27,11 @@ private static void Main(string[] args) {
try {
Log.Information("Starting InfiniFrame MudBlazor example...");
- var appBuilder = InfiniFrameBlazorAppBuilder.CreateDefault(args);
-
- appBuilder.Services
+ InfiniFrameApplicationBuilder builder = InfiniFrameApplication.CreateBuilder(args);
+ builder.WithWindow(window => window
+ .SetIconFile("wwwroot/favicon.ico")
+ .RegisterOpenExternalTargetWebMessageHandler());
+ builder.Services
.AddLogging(config => {
config.ClearProviders();
config.AddSerilog();
@@ -37,17 +41,12 @@ private static void Main(string[] args) {
.MinimumLevel.Debug();
})
.AddMudServices();
-
- appBuilder.RootComponents.Add("app");
-
- appBuilder.WindowBuilder
- .SetIconFile("wwwroot/favicon.ico")
- .RegisterOpenExternalTargetWebMessageHandler();
-
- InfiniFrameSingleFile.AddSingleFileRequirements(appBuilder);
-
- Log.Information("Building InfiniFrame application...");
- InfiniFrameBlazorApp application = appBuilder.Build();
+ InfiniFrameApplication application = builder
+ .UseBlazorWebView(configuration => {
+ configuration.RootComponents.Add("app");
+ configuration.AddSingleFileRequirements();
+ })
+ .Build();
Log.Information("Running application...");
application.Run();
diff --git a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.React/Program.cs b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.React/Program.cs
index e2817d90c..c37e87912 100644
--- a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.React/Program.cs
+++ b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.React/Program.cs
@@ -1,5 +1,6 @@
-using InfiniFrame;
using System.Drawing;
+using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.SingleFile;
namespace InfiniFrameExample.SingleFileExe.React;
@@ -9,14 +10,15 @@ public static class Program {
public static void Main(string[] args) {
InfiniFrameSingleFile.Initialize();
- IInfiniFrameWindowBuilder builder = InfiniFrameWindowBuilder.Create()
- .SetTitle("InfiniFrame + React")
- .SetSize(new Size(960, 640))
- .CenteredOnMainMonitor();
-
- builder.AddSingleFileRequirements();
-
- IInfiniFrameWindow window = builder.Build();
- window.WaitForClose();
+ InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(builder => {
+ builder
+ .SetTitle("InfiniFrame + React")
+ .SetSize(new Size(960, 640))
+ .CenteredOnMainMonitor();
+ builder.AddSingleFileRequirements();
+ })
+ .Build()
+ .Run();
}
}
diff --git a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.Vue/Program.cs b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.Vue/Program.cs
index 866c60e20..729e22acc 100644
--- a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.Vue/Program.cs
+++ b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe.Vue/Program.cs
@@ -1,8 +1,9 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-using InfiniFrame;
using System.Drawing;
+using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.SingleFile;
namespace InfiniFrameExample.SingleFileExe.Vue;
@@ -14,14 +15,15 @@ public static class Program {
public static void Main(string[] args) {
InfiniFrameSingleFile.Initialize();
- IInfiniFrameWindowBuilder builder = InfiniFrameWindowBuilder.Create()
- .SetTitle("InfiniFrame + Vue")
- .SetSize(new Size(960, 640))
- .CenteredOnMainMonitor();
-
- builder.AddSingleFileRequirements();
-
- IInfiniFrameWindow window = builder.Build();
- window.WaitForClose();
+ InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(builder => {
+ builder
+ .SetTitle("InfiniFrame + Vue")
+ .SetSize(new Size(960, 640))
+ .CenteredOnMainMonitor();
+ builder.AddSingleFileRequirements();
+ })
+ .Build()
+ .Run();
}
}
diff --git a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe/Program.cs b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe/Program.cs
index 10387ee05..61a9fab20 100644
--- a/examples/SingleFileExe/InfiniFrameExample.SingleFileExe/Program.cs
+++ b/examples/SingleFileExe/InfiniFrameExample.SingleFileExe/Program.cs
@@ -1,8 +1,9 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-using InfiniFrame;
using System.Drawing;
+using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.SingleFile;
namespace InfiniFrameExample.SingleFileExe;
@@ -14,15 +15,15 @@ public static class Program {
public static void Main(string[] args) {
InfiniFrameSingleFile.Initialize();
- IInfiniFrameWindowBuilder builder = InfiniFrameWindowBuilder.Create()
- .SetTitle("InfiniFrame Embedded wwwroot")
- .SetSize(new Size(960, 640))
- .CenteredOnMainMonitor();
-
- builder.AddSingleFileRequirements();
-
- IInfiniFrameWindow window = builder.Build();
-
- window.WaitForClose();
+ InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(builder => {
+ builder
+ .SetTitle("InfiniFrame Embedded wwwroot")
+ .SetSize(new Size(960, 640))
+ .CenteredOnMainMonitor();
+ builder.AddSingleFileRequirements();
+ })
+ .Build()
+ .Run();
}
}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Program.cs b/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Program.cs
index 65969dbf3..f660c0de8 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Program.cs
+++ b/examples/WebApp/InfiniFrameExample.WebApp.Blazor/Program.cs
@@ -1,11 +1,13 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
+using System.Drawing;
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
+using InfiniFrame.Window.Features.WebMessaging.Handlers;
using InfiniFrameExample.WebApp.Blazor.Components;
using Serilog;
-using System.Drawing;
namespace InfiniFrameExample.WebApp.Blazor;
// ---------------------------------------------------------------------------------------------------------------------
@@ -17,9 +19,16 @@ private static void Main(string[] args) {
// -------------------------------------------------------------------------------------------------------------
// Builder
// -------------------------------------------------------------------------------------------------------------
- InfiniFrameWebApplicationBuilder appBuilder = InfiniFrameWebApplication.CreateBuilder(args);
+ InfiniFrameApplicationBuilder builder = InfiniFrameApplication.CreateBuilder(args);
- appBuilder.Services
+ builder.WithWindow(window => window
+ .SetIconFile("wwwroot/favicon.ico")
+ .SetLocation(new Point(100, 100))
+ .SetSize(new Size(800, 600))
+ .RegisterOpenExternalTargetWebMessageHandler()
+ );
+
+ builder.Services
.AddLogging(config => {
config.ClearProviders();
config.AddSerilog();
@@ -31,7 +40,7 @@ private static void Main(string[] args) {
.AddRazorComponents()
.AddInteractiveServerComponents();
- appBuilder.Services.AddHttpClient("ServerApi", (sp, client) => {
+ builder.Services.AddHttpClient("ServerApi", configureClient: (sp, client) => {
var config = sp.GetRequiredService();
// Prefer ASPNETCORE_URLS, then "urls", then a fallback
@@ -45,44 +54,24 @@ private static void Main(string[] args) {
client.BaseAddress = new Uri(baseUrl);
});
- appBuilder.Services.AddScoped(sp => sp.GetRequiredService().CreateClient("ServerApi"));
-
- appBuilder.Services.AddInfiniFrameJs();
-
- appBuilder.WebApp.WebHost.UseStaticWebAssets();
+ builder.Services.AddScoped(sp => sp.GetRequiredService().CreateClient("ServerApi"));
- appBuilder.WindowBuilder
- // .SetTransparent(true)
- // .SetChromeless(true)
- // .SetResizable(true)
- .SetIconFile("wwwroot/favicon.ico")
- // .Center()
- // .SetUseOsDefaultSize(true)
- // .SetUseOsDefaultLocation(true);
- // .SetTitle("InfiniLore InfiniFrame.Blazor Sample")
- .SetLocation(new Point(100, 100))
- .SetSize(new Size(800, 600))
- .RegisterOpenExternalTargetWebMessageHandler()
- // .SetMaxSize(new Size(800, 600))
- // .SetMinSize(new Size(600, 400))
- ;
+ builder.Services.AddInfiniFrameJs();
- // -------------------------------------------------------------------------------------------------------------
- // App
- // -------------------------------------------------------------------------------------------------------------
- InfiniFrameWebApplication application = appBuilder.Build();
- application.UseAutoServerClose();
-
- WebApplication webApp = application.WebApp;
+ builder.UseWebServer(web => {
- webApp.UseRouting();
+ web.WebHost.UseStaticWebAssets();
- webApp.UseAntiforgery();
- webApp.MapStaticAssets();
-
- webApp.MapRazorComponents()
- .AddInteractiveServerRenderMode();
+ web.ConfigureWebApplication(webApp => {
+ webApp.UseRouting();
+ webApp.UseAntiforgery();
+ webApp.MapStaticAssets();
+ webApp.MapRazorComponents()
+ .AddInteractiveServerRenderMode();
+ });
+ });
+ InfiniFrameApplication application = builder.Build();
application.Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.Blazor/README.md b/examples/WebApp/InfiniFrameExample.WebApp.Blazor/README.md
index 848223dc4..c9793bdb0 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.Blazor/README.md
+++ b/examples/WebApp/InfiniFrameExample.WebApp.Blazor/README.md
@@ -4,12 +4,12 @@ Demonstrates hosting a full ASP.NET Core Blazor Server application inside a nati
## What it shows
-- `InfiniFrameWebApplication.CreateBuilder()` entry point
+- `InfiniFrameApplication.CreateBuilder()` entry point
- Blazor Server with `AddRazorComponents()` + `AddInteractiveServerComponents()`
- `HttpClient` factory configured to point at the local Kestrel server
- `AddInfiniFrameJs()` service registration for Blazor component interop
- `RegisterOpenExternalTargetWebMessageHandler()` links with `target="_blank"` open in the default browser
-- `UseAutoServerClose()` server stops when the window is closed
+- Application-owned lifecycle stops the server when the window is closed
- Serilog with async console sink
## Run
@@ -21,19 +21,19 @@ dotnet run --project examples/InfiniFrameExample.WebApp.Blazor
## Key code
```csharp
-InfiniFrameWebApplicationBuilder builder = InfiniFrameWebApplication.CreateBuilder(args);
+InfiniFrameApplicationBuilder builder = InfiniFrameApplication.CreateBuilder(args);
-builder.WebApp.Services.AddRazorComponents()
+builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
-builder.WebApp.Services.AddInfiniFrameJs();
+builder.Services.AddInfiniFrameJs();
-builder.Window
+builder.WithWindow(window => window
.SetSize(new Size(800, 600))
- .RegisterOpenExternalTargetWebMessageHandler();
+ .RegisterOpenExternalTargetWebMessageHandler());
-InfiniFrameWebApplication app = builder.Build();
-app.UseAutoServerClose();
-app.WebApp.MapRazorComponents().AddInteractiveServerRenderMode();
+builder.UseWebServer(web => web.ConfigureWebApplication(application =>
+ application.MapRazorComponents().AddInteractiveServerRenderMode()));
+InfiniFrameApplication app = builder.Build();
app.Run();
```
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.React/Program.cs b/examples/WebApp/InfiniFrameExample.WebApp.React/Program.cs
index b32b42dcc..3e88cbcd3 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.React/Program.cs
+++ b/examples/WebApp/InfiniFrameExample.WebApp.React/Program.cs
@@ -2,8 +2,9 @@
// Imports
// ---------------------------------------------------------------------------------------------------------------------
using InfiniFrame;
-using InfiniFrame.WebServer;
using System.Drawing;
+using InfiniFrame.Application;
+using InfiniFrame.WebServer;
namespace InfiniFrameExample.WebApp.React;
// ---------------------------------------------------------------------------------------------------------------------
@@ -17,11 +18,10 @@ private sealed class WebMessageCounter {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameWebApplicationBuilder appBuilder = InfiniFrameWebApplication.CreateBuilder(args);
- // WebApplicationBuilder appBuilder = builder.WebApp;
- appBuilder.WebApp.Services.AddSingleton();
-
- appBuilder.WindowBuilder
+ InfiniFrameApplicationBuilder rootBuilder = InfiniFrameApplication.CreateBuilder(args);
+ rootBuilder.Services.AddSingleton();
+ InfiniFrameApplication application = rootBuilder
+ .WithWindow(window => window
.UseOsDefaultSize(false)
.SetResizable()
.CenteredOnMainMonitor()
@@ -39,19 +39,18 @@ public static void Main(string[] args) {
])
, "text/javascript")
)
- .RegisterWebMessageReceivedHandler((IInfiniFrameWindow window, string message, WebMessageCounter counter) => {
+ .RegisterWebMessageReceivedHandler((IInfiniFrameWindow infiniFrameWindow, string message, WebMessageCounter counter) => {
int count = counter.Increment();
string response = $"[{count}] Received message: \"{message}\"";
- window.SendWebMessage(response);
- });
-
- InfiniFrameWebApplication application = appBuilder.Build();
-
- application.UseAutoServerClose();
-
- application.WebApp.UseStaticFiles();
- application.WebApp.MapStaticAssets();
+ infiniFrameWindow.SendWebMessage(response);
+ }))
+ .UseWebServer(builder => {
+ builder.ConfigureWebApplication(webApp => {
+ webApp.MapStaticAssets();
+ });
+ })
+ .Build();
application.Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.React/README.md b/examples/WebApp/InfiniFrameExample.WebApp.React/README.md
index 590377a09..9b770d96e 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.React/README.md
+++ b/examples/WebApp/InfiniFrameExample.WebApp.React/README.md
@@ -7,7 +7,7 @@ Demonstrates a React frontend served by ASP.NET Core inside an InfiniFrame windo
- `RegisterCustomSchemeHandler("app", ...)` intercepts `app://` requests and returns dynamically generated JavaScript
- `RegisterWebMessageReceivedHandler(...)` receives messages from JavaScript, increments a counter, and echoes a response back via `SendWebMessage`
- A singleton `WebMessageCounter` service accessed inside the message handler via DI
-- `UseAutoServerClose()` server stops when the window is closed
+- Application-owned lifecycle stops the server when the window is closed
## Run
diff --git a/examples/WebApp/InfiniFrameExample.WebApp.Vue/Program.cs b/examples/WebApp/InfiniFrameExample.WebApp.Vue/Program.cs
index 50a6541e9..93e073071 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp.Vue/Program.cs
+++ b/examples/WebApp/InfiniFrameExample.WebApp.Vue/Program.cs
@@ -1,9 +1,11 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
+using System.Drawing;
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
-using System.Drawing;
+using InfiniFrame.Window.Features.WebMessaging.Handlers;
namespace InfiniFrameExample.WebApp.Vue;
// ---------------------------------------------------------------------------------------------------------------------
@@ -12,37 +14,35 @@ namespace InfiniFrameExample.WebApp.Vue;
public static class Program {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameWebApplicationBuilder appBuilder = InfiniFrameWebApplication.CreateBuilder(args);
- // WebApplicationBuilder appBuilder = builder.WebApp;
-
- if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux()) appBuilder.WindowBuilder.Debugging.SetRemoteDebuggingPort(9222);
-
- appBuilder.WindowBuilder
- .CenteredOnMainMonitor()
- // .SetTransparent(true)
- // .SetUseOsDefaultSize(false)
- .SetTitle("InfiniLore InfiniFrame.NET VUE Sample")
- .SetSize(new Size(800, 600))
- .SetLocation(1000, 0)
- .RegisterFullScreenWebMessageHandler()
- .RegisterOpenExternalTargetWebMessageHandler()
- .RegisterTitleChangedWebMessageHandler()
- .RegisterWindowManagementWebMessageHandler()
- .RegisterWebMessageReceivedHandler((_, message) => {
- // ReSharper disable twice UnusedVariable
- string response = $"Received message: \"{message}\"";
-
- // ... do something with the message
+ InfiniFrameApplication application = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow(window => {
+ if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux()) window.Debugging.SetRemoteDebuggingPort(9222);
+ window
+ .CenteredOnMainMonitor()
+ // .SetTransparent(true)
+ // .SetUseOsDefaultSize(false)
+ .SetTitle("InfiniLore InfiniFrame.NET VUE Sample")
+ .SetSize(new Size(800, 600))
+ .SetLocation(1000, 0)
+ .RegisterFullScreenWebMessageHandler()
+ .RegisterOpenExternalTargetWebMessageHandler()
+ .RegisterTitleChangedWebMessageHandler()
+ .RegisterWindowManagementWebMessageHandler()
+ .RegisterWebMessageReceivedHandler((_, message) => {
+ // ReSharper disable twice UnusedVariable
+ string response = $"Received message: \"{message}\"";
+
+ // ... do something with the message
+ })
+ ;
})
- ;
-
- InfiniFrameWebApplication application = appBuilder.Build();
-
- application.UseAutoServerClose();
-
- application.WebApp.UseStaticFiles();
- application.WebApp.MapStaticAssets();
+ .UseWebServer(builder => {
+ builder.ConfigureWebApplication(webApp => {
+ webApp.MapStaticAssets();
+ });
+ })
+ .Build();
application.Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp/Program.cs b/examples/WebApp/InfiniFrameExample.WebApp/Program.cs
index 8de617cc8..4de4afb2c 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp/Program.cs
+++ b/examples/WebApp/InfiniFrameExample.WebApp/Program.cs
@@ -2,6 +2,7 @@
// Imports
// ---------------------------------------------------------------------------------------------------------------------
using InfiniFrame;
+using InfiniFrame.Application;
using InfiniFrame.WebServer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -14,23 +15,20 @@ namespace InfiniFrameExample.WebApp;
public static class Program {
[STAThread]
public static void Main(string[] args) {
- InfiniFrameWebApplicationBuilder builder =
- InfiniFrameWebApplication.CreateBuilder(args);
-
- builder.WebApp.WebHost.UseUrls("http://127.0.0.1:5055");
- builder.WindowBuilder
- .SetStartPageUrl("http://127.0.0.1:5055")
- .SetTitle("InfiniFrame WebServer Repro")
- .SetIconFile("wwwroot/favicon.ico");
-
- InfiniFrameWebApplication app = builder.Build();
- app.UseAutoServerClose();
-
- app.WebApp.MapGet("/", handler: () => Results.Content(
- "InfiniFrame loaded",
- "text/html"
- ));
+ InfiniFrameApplication app = InfiniFrameApplication.CreateBuilder(args)
+ .WithWindow("web", configure: window => window
+ .SetStartPageUrl("http://127.0.0.1:5055")
+ .SetTitle("InfiniFrame WebServer Repro")
+ .SetIconFile("wwwroot/favicon.ico"))
+ .UseWebServer("web", configure: builder => {
+ builder.WebHost.UseUrls("http://127.0.0.1:5055");
+ builder.ConfigureWebApplication(webApp => webApp.MapGet("/", handler: () => Results.Content(
+ "InfiniFrame loaded",
+ "text/html"
+ )));
+ })
+ .Build();
app.Run();
}
-}
\ No newline at end of file
+}
diff --git a/examples/WebApp/InfiniFrameExample.WebApp/README.md b/examples/WebApp/InfiniFrameExample.WebApp/README.md
index 6c1c51fa6..67eb03dfa 100644
--- a/examples/WebApp/InfiniFrameExample.WebApp/README.md
+++ b/examples/WebApp/InfiniFrameExample.WebApp/README.md
@@ -4,9 +4,9 @@ Demonstrates InfiniFrame's built-in ASP.NET Core web server integration. A local
## What It Shows
-- `InfiniFrameWebApplicationBuilder` and `InfiniFrameWebApplication` API
+- `InfiniFrameApplicationBuilder` and `InfiniFrameApplication` API
- ASP.NET Core minimal APIs (`MapGet`)
-- `UseAutoServerClose()` for graceful shutdown
+- Application-owned lifecycle for graceful shutdown
- Window-to-server DI integration
## Run
diff --git a/scripts/clean.ps1 b/scripts/clean.ps1
index a9d2913bf..d6d4fe45a 100644
--- a/scripts/clean.ps1
+++ b/scripts/clean.ps1
@@ -24,10 +24,6 @@ $ExtraPaths = @(
"../src/InfiniFrame.NativeBridge/build",
"../src/InfiniFrame.NativeBridge/artifacts",
"../src/InfiniFrame.Js/node_modules",
- "../src/InfiniFrame.NativeBridge/Native/cmake-build-debug-linux",
- "../src/InfiniFrame.NativeBridge/Native/cmake-build-debug-windows",
- "../src/InfiniFrame.NativeBridge/Native/cmake-build-release-linux",
- "../src/InfiniFrame.NativeBridge/Native/cmake-build-release-windows",
"../src/InfiniFrame.NativeBridge/Native/packages"
)
@@ -40,4 +36,4 @@ foreach ($RelativePath in $ExtraPaths) {
}
}
-Write-Host "Done cleaning bin/obj folders and extra build artifacts."
\ No newline at end of file
+Write-Host "Done cleaning bin/obj folders and extra build artifacts."
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 90f2fe50f..0adc5409d 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -40,16 +40,22 @@
+
+
+
+
-
+
+
+
@@ -77,4 +83,4 @@
-
\ No newline at end of file
+
diff --git a/src/InfiniFrame.Application/ApplicationConfiguration.cs b/src/InfiniFrame.Application/ApplicationConfiguration.cs
new file mode 100644
index 000000000..058f2a515
--- /dev/null
+++ b/src/InfiniFrame.Application/ApplicationConfiguration.cs
@@ -0,0 +1,22 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using InfiniFrame.NativeBridge.Parameters.Application;
+
+namespace InfiniFrame.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+internal sealed record ApplicationConfiguration(
+ string? WebView2RuntimePath = null,
+ string? NotificationRegistrationId = null,
+ string? AppUserModelId = null,
+ string? DefaultNotificationIcon = null
+) {
+ internal InfiniFrameNativeApplicationParameters ToNativeParameters() => new() {
+ WebView2RuntimePath = WebView2RuntimePath,
+ NotificationRegistrationId = NotificationRegistrationId,
+ AppUserModelId = AppUserModelId,
+ DefaultNotificationIcon = DefaultNotificationIcon
+ };
+}
diff --git a/src/InfiniFrame.Application/InfiniFrame.Application.csproj b/src/InfiniFrame.Application/InfiniFrame.Application.csproj
new file mode 100644
index 000000000..0203b192b
--- /dev/null
+++ b/src/InfiniFrame.Application/InfiniFrame.Application.csproj
@@ -0,0 +1,21 @@
+
+
+ InfiniLore.InfiniFrame.Application
+ InfiniFrame application lifecycle and window orchestration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/InfiniFrame.Application/InfiniFrameApplication.cs b/src/InfiniFrame.Application/InfiniFrameApplication.cs
new file mode 100644
index 000000000..161043667
--- /dev/null
+++ b/src/InfiniFrame.Application/InfiniFrameApplication.cs
@@ -0,0 +1,536 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using InfiniFrame.NativeBridge;
+using InfiniFrame.NativeBridge.Handles;
+using FluentValidation;
+using InfiniFrame.NativeBridge.Parameters.Application;
+using InfiniFrame.Utilities;
+using InfiniFrame.Window.Builder;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace InfiniFrame.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+///
+/// Application-level owner for lazily built InfiniFrame windows.
+///
+public sealed class InfiniFrameApplication : IInfiniFrameApplication {
+ private readonly ILogger logger;
+ private readonly NativeApplicationHandle _nativeHandle;
+ private readonly object _gate = new();
+ private readonly List<(
+ string? Id,
+ Action? Configure,
+ InfiniFrameWindowBuilder? Builder,
+ IServiceProvider? Provider
+ )> _registrations = [];
+ private readonly List> _shutdownActions = [];
+ private readonly List> _startupActions = [];
+ private readonly Dictionary _windows = [];
+ private IServiceProvider? _serviceProvider;
+ private int _disposed;
+ private int _runState;
+ private bool _built;
+ private int _shutdownRequested;
+
+ private InfiniFrameApplication(ILogger logger, ApplicationConfiguration configuration) {
+ this.logger = logger;
+ InfiniFrameNativeApplicationParameters parameters = configuration.ToNativeParameters();
+ new InfiniFrameNativeApplicationParametersValidator().ValidateAndThrow(parameters);
+
+ InfiniFrameNativeInteropStatus status = InfiniFrameNative.ApplicationConstructor(out IntPtr handle);
+ if (status != InfiniFrameNativeInteropStatus.Success)
+ throw new InfiniFrameNativeInteropException(InfiniFrameNative.GetLastErrorMessage() ?? "Could not create native application.");
+
+ _nativeHandle = new NativeApplicationHandle(handle);
+ InfiniFrameNativeInteropStatus configureStatus = InfiniFrameNative.ApplicationConfigure(
+ _nativeHandle.DangerousGetHandle(),
+ in parameters
+ );
+ if (configureStatus != InfiniFrameNativeInteropStatus.Success) {
+ _nativeHandle.Dispose();
+ throw new InfiniFrameNativeInteropException(
+ InfiniFrameNative.GetLastErrorMessage() ?? "Could not configure native application.");
+ }
+ }
+
+ /// Creates an application without requiring a dependency-injection container.
+ public static InfiniFrameApplication Initialize()
+ => new(NullLogger.Instance, new ApplicationConfiguration());
+
+ internal static InfiniFrameApplication Initialize(ApplicationConfiguration configuration)
+ => new(NullLogger.Instance, configuration);
+
+ public static InfiniFrameApplicationBuilder CreateBuilder(string[]? args = null)
+ => new(args);
+
+ /// Creates an application using the supplied logger.
+ public static InfiniFrameApplication Initialize(ILogger logger) {
+ ArgumentNullException.ThrowIfNull(logger);
+ return new InfiniFrameApplication(logger, new ApplicationConfiguration());
+ }
+
+ public Guid Id { get; } = Guid.NewGuid();
+ public IntPtr ApplicationHandle => _nativeHandle.DangerousGetHandle();
+ public bool IsShutdownRequested => Volatile.Read(ref _shutdownRequested) != 0;
+ public event Action? WindowCreated;
+ public event Action? WindowDestroyed;
+
+ ///
+ public void RegisterWindow(Action configure) {
+ ArgumentNullException.ThrowIfNull(configure);
+ RegisterWindowCore(null, configure, null);
+ }
+
+ ///
+ public void RegisterWindow(string id, Action configure) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ ArgumentNullException.ThrowIfNull(configure);
+ RegisterWindowCore(id, configure, null);
+ }
+
+ /// Registers an unnamed window and returns this application for fluent configuration.
+ public InfiniFrameApplication WithWindow(Action configure) {
+ RegisterWindow(configure);
+ return this;
+ }
+
+ /// Registers a named window and returns this application for fluent configuration.
+ public InfiniFrameApplication WithWindow(string id, Action configure) {
+ RegisterWindow(id, configure);
+ return this;
+ }
+
+ ///
+ public IInfiniFrameWindow GetWindow(string id) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ lock (_gate) {
+ EnsureBuilt();
+ return _windows.TryGetValue(id, out IInfiniFrameWindow? window)
+ ? window
+ : throw new KeyNotFoundException($"Window with id '{id}' was not found.");
+ }
+ }
+
+ ///
+ public IInfiniFrameWindow? TryGetWindow(string id) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ lock (_gate) return _built && _windows.TryGetValue(id, out IInfiniFrameWindow? window) ? window : null;
+ }
+
+ ///
+ public IReadOnlyList Windows {
+ get {
+ lock (_gate) return _windows.Values.ToArray();
+ }
+ }
+
+ ///
+ public void Run() {
+ BeginRun();
+ try {
+ EnsureWindowsStaThread();
+ RegisterNativeApplication();
+ if (IsShutdownRequested) return;
+ StartRegisteredComponents();
+ BuildAllWindows();
+ RunNativeLoop();
+ }
+ finally {
+ Dispose();
+ }
+ }
+
+ ///
+ public async Task RunAsync(CancellationToken ct = default) {
+ BeginRun();
+ await using CancellationTokenRegistration registration = ct.Register(Shutdown);
+ Task? uiTask = null;
+ try {
+ ct.ThrowIfCancellationRequested();
+ await StartRegisteredComponentsAsync().ConfigureAwait(false);
+ ct.ThrowIfCancellationRequested();
+ var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var uiThread = new Thread(() => {
+ try {
+ if (IsShutdownRequested) {
+ completion.TrySetResult();
+ return;
+ }
+
+ RegisterNativeApplication();
+ if (IsShutdownRequested) {
+ completion.TrySetResult();
+ return;
+ }
+
+ BuildAllWindows();
+ if (IsShutdownRequested) {
+ CloseAll();
+ }
+
+ RunNativeLoop();
+ completion.TrySetResult();
+ }
+ catch (Exception exception) when (ExceptionsUtility.IsNonFatalException(exception)) {
+ completion.TrySetException(exception);
+ }
+ }) {
+ IsBackground = true,
+ Name = "InfiniFrame Application UI Thread"
+ };
+
+ if (OperatingSystem.IsWindows())
+ uiThread.SetApartmentState(ApartmentState.STA);
+ uiThread.Start();
+ uiTask = completion.Task;
+ await uiTask.ConfigureAwait(false);
+ }
+ finally {
+ try {
+ if (uiTask is not null)
+ await uiTask.ConfigureAwait(false);
+ }
+ finally {
+ await DisposeAsync().ConfigureAwait(false);
+ }
+ }
+ }
+
+ ///
+ public void Shutdown() {
+ if (Volatile.Read(ref _disposed) != 0) return;
+ if (Interlocked.Exchange(ref _shutdownRequested, 1) != 0) return;
+ InfiniFrameNative.ApplicationShutdown(_nativeHandle.DangerousGetHandle());
+ CloseAll();
+ }
+
+ ///
+ public void CloseAll() {
+ foreach (IInfiniFrameWindow window in Windows.ToArray()) {
+ try {
+ window.Close();
+ }
+ catch (Exception exception) when (exception is ObjectDisposedException or InvalidOperationException) {
+ logger.LogDebug(exception, "Window was already unavailable during application shutdown.");
+ }
+ }
+ }
+
+ private void RegisterNativeApplication() {
+ InfiniFrameNativeInteropStatus status = InfiniFrameNative.ApplicationRegister(_nativeHandle.DangerousGetHandle());
+ if (status != InfiniFrameNativeInteropStatus.Success)
+ throw new InfiniFrameNativeInteropException(
+ InfiniFrameNative.GetLastErrorMessage() ?? "Could not register native application.");
+ }
+
+ ///
+ public void Dispose() {
+ if (Interlocked.Exchange(ref _disposed, 1) != 0) return;
+
+ IInfiniFrameWindow[] windows;
+ lock (_gate) {
+ windows = _windows.Values.ToArray();
+ }
+
+ Exception? windowDisposalFailure = null;
+ foreach (IInfiniFrameWindow window in windows) {
+ try {
+ (window as IDisposable)?.Dispose();
+ if (window.LifecycleState != InfiniFrameWindowLifecycleState.Disposed)
+ throw new InvalidOperationException("A window did not complete native disposal.");
+ RemoveTrackedWindow(window);
+ }
+ catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException) {
+ logger.LogWarning(ex, "Failed to dispose an application window.");
+ windowDisposalFailure ??= ex;
+ }
+ }
+ if (windowDisposalFailure is not null) {
+ Volatile.Write(ref _disposed, 0);
+ throw new InvalidOperationException("The application could not dispose all windows.", windowDisposalFailure);
+ }
+ lock (_gate) {
+ _windows.Clear();
+ _registrations.Clear();
+ }
+ StopRegisteredComponents();
+ _nativeHandle.Dispose();
+ if (_serviceProvider is IAsyncDisposable asyncServiceProvider)
+ asyncServiceProvider.DisposeAsync().AsTask().GetAwaiter().GetResult();
+ else (_serviceProvider as IDisposable)?.Dispose();
+ _serviceProvider = null;
+ }
+
+ ///
+ public async ValueTask DisposeAsync() {
+ if (Interlocked.Exchange(ref _disposed, 1) != 0) return;
+
+ IInfiniFrameWindow[] windows;
+ lock (_gate) {
+ windows = _windows.Values.ToArray();
+ }
+
+ Exception? windowDisposalFailure = null;
+ foreach (IInfiniFrameWindow window in windows) {
+ try {
+ if (window is IAsyncDisposable asyncDisposable)
+ await asyncDisposable.DisposeAsync().ConfigureAwait(false);
+ else (window as IDisposable)?.Dispose();
+ if (window.LifecycleState != InfiniFrameWindowLifecycleState.Disposed)
+ throw new InvalidOperationException("A window did not complete native disposal.");
+ RemoveTrackedWindow(window);
+ }
+ catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException) {
+ logger.LogWarning(ex, "Failed to asynchronously dispose an application window.");
+ windowDisposalFailure ??= ex;
+ }
+ }
+ if (windowDisposalFailure is not null) {
+ Volatile.Write(ref _disposed, 0);
+ throw new InvalidOperationException("The application could not dispose all windows.", windowDisposalFailure);
+ }
+ lock (_gate) {
+ _windows.Clear();
+ _registrations.Clear();
+ }
+ await StopRegisteredComponentsAsync().ConfigureAwait(false);
+ _nativeHandle.Dispose();
+ if (_serviceProvider is IAsyncDisposable asyncServiceProvider)
+ await asyncServiceProvider.DisposeAsync().ConfigureAwait(false);
+ else (_serviceProvider as IDisposable)?.Dispose();
+ _serviceProvider = null;
+ }
+
+ internal void RegisterWindowBuilder(string id, InfiniFrameWindowBuilder builder, IServiceProvider? provider = null) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ ArgumentNullException.ThrowIfNull(builder);
+ RegisterWindowCore(id, null, builder, provider);
+ }
+
+ private void BeginRun() {
+ ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
+ if (Interlocked.CompareExchange(ref _runState, 1, 0) != 0)
+ throw new InvalidOperationException("The InfiniFrame application can only be run once.");
+ }
+
+ internal void ValidateWindowIntegrationTargets(IReadOnlyList? ids, string integrationName) {
+ lock (_gate) {
+ GetIntegrationTargets(ids, integrationName);
+ }
+ }
+
+ internal void ApplyWindowIntegration(
+ IReadOnlyList? ids,
+ string integrationName,
+ Action configure
+ ) {
+ ArgumentNullException.ThrowIfNull(configure);
+ lock (_gate) {
+ foreach (int index in GetIntegrationTargets(ids, integrationName)) {
+ (string? id, Action? existing, InfiniFrameWindowBuilder? builder, IServiceProvider? provider) = _registrations[index];
+ _registrations[index] = (id, target => {
+ existing?.Invoke(target);
+ configure(target);
+ }, builder, provider);
+ }
+ }
+ }
+
+ internal void RegisterShutdownAction(Func action) {
+ ArgumentNullException.ThrowIfNull(action);
+ lock (_gate) _shutdownActions.Add(action);
+ }
+
+ internal void RegisterStartupAction(Func action) {
+ ArgumentNullException.ThrowIfNull(action);
+ lock (_gate) _startupActions.Add(action);
+ }
+
+ private void RegisterWindowCore(
+ string? id,
+ Action? configure,
+ InfiniFrameWindowBuilder? builder,
+ IServiceProvider? provider = null
+ ) {
+ ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
+ lock (_gate) {
+ if (_built || Volatile.Read(ref _runState) != 0)
+ throw new InvalidOperationException("Cannot register windows after the application run has started.");
+ if (id is not null && _registrations.Any(registration => registration.Id == id))
+ throw new ArgumentException($"A window with id '{id}' is already registered.", nameof(id));
+ _registrations.Add((id, configure, builder, provider));
+ }
+ }
+
+ private IReadOnlyList GetIntegrationTargets(IReadOnlyList? ids, string integrationName) {
+ if (ids is null || ids.Count == 0) {
+ if (_registrations.Count == 0)
+ throw new InvalidOperationException(
+ $"Cannot bind {integrationName}: no windows are registered. Register a window before configuring the integration.");
+ if (_registrations.Count != 1)
+ throw new InvalidOperationException(
+ $"Cannot bind {integrationName}: {_registrations.Count} windows are registered. Specify explicit window IDs.");
+ return [0];
+ }
+
+ if (ids.Any(string.IsNullOrWhiteSpace))
+ throw new ArgumentException($"{integrationName} window IDs must not be null or whitespace.", nameof(ids));
+ if (ids.Count != ids.Distinct(StringComparer.Ordinal).Count())
+ throw new ArgumentException($"{integrationName} window IDs must not contain duplicates.", nameof(ids));
+
+ var targets = new List(ids.Count);
+ foreach (string id in ids) {
+ int index = _registrations.FindIndex(registration => string.Equals(registration.Id, id, StringComparison.Ordinal));
+ if (index < 0)
+ throw new InvalidOperationException(
+ $"Cannot bind {integrationName}: no registered window has ID '{id}'.");
+ targets.Add(index);
+ }
+ return targets;
+ }
+
+ private void BuildAllWindows() {
+ (string Id, IInfiniFrameWindow Window)[] built;
+ lock (_gate) {
+ ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
+ if (_built) return;
+
+ var windows = new List<(string Id, IInfiniFrameWindow Window)>();
+ try {
+ foreach ((string? id, Action? configure, InfiniFrameWindowBuilder? registeredBuilder, IServiceProvider? provider) in _registrations) {
+ InfiniFrameWindowBuilder builder = registeredBuilder ?? new InfiniFrameWindowBuilder();
+ configure?.Invoke(builder);
+ builder.SetApplicationHandle(_nativeHandle.DangerousGetHandle());
+ string windowId = id ?? Guid.NewGuid().ToString("N");
+ windows.Add((windowId, builder.Build(provider ?? _serviceProvider)));
+ }
+
+ foreach ((string id, IInfiniFrameWindow window) in windows) {
+ _windows.Add(id, window);
+ }
+ _registrations.Clear();
+ _built = true;
+ built = [.. windows];
+ }
+ catch {
+ foreach ((_, IInfiniFrameWindow window) in windows) (window as IDisposable)?.Dispose();
+ throw;
+ }
+ }
+
+ foreach ((_, IInfiniFrameWindow window) in built)
+ WindowCreated?.Invoke(window);
+ foreach ((string id, IInfiniFrameWindow window) in built)
+ _ = TrackNaturalWindowCloseAsync(id, window);
+ }
+
+ private async Task TrackNaturalWindowCloseAsync(string id, IInfiniFrameWindow window) {
+ try {
+ await window.Features.Lifecycle.WaitForTeardownAsync().ConfigureAwait(false);
+ if (window is IAsyncDisposable asyncDisposable)
+ await asyncDisposable.DisposeAsync().ConfigureAwait(false);
+ else (window as IDisposable)?.Dispose();
+ RemoveTrackedWindow(id, window);
+ }
+ catch (Exception exception) when (exception is not OutOfMemoryException and not StackOverflowException) {
+ logger.LogDebug(exception, "Could not finalize a naturally closed application window.");
+ }
+ }
+
+ private void RemoveTrackedWindow(IInfiniFrameWindow window) {
+ string? id;
+ lock (_gate) {
+ id = _windows.FirstOrDefault(pair => ReferenceEquals(pair.Value, window)).Key;
+ if (id is null) return;
+ _windows.Remove(id);
+ }
+ WindowDestroyed?.Invoke(window);
+ }
+
+ private void RemoveTrackedWindow(string id, IInfiniFrameWindow window) {
+ lock (_gate) {
+ if (!_windows.TryGetValue(id, out IInfiniFrameWindow? tracked) || !ReferenceEquals(tracked, window)) return;
+ _windows.Remove(id);
+ }
+ WindowDestroyed?.Invoke(window);
+ }
+
+ private void EnsureBuilt()
+ {
+ ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
+ if (!_built) throw new InvalidOperationException("Windows have not been built yet. Call Run() or RunAsync() first.");
+ }
+
+ private void RunNativeLoop() {
+ if (OperatingSystem.IsWindows()) {
+ InfiniFrameNativeInteropStatus status = InfiniFrameNative.ApplicationRun(_nativeHandle.DangerousGetHandle());
+ if (status != InfiniFrameNativeInteropStatus.Success)
+ throw new InfiniFrameNativeInteropException(
+ InfiniFrameNative.GetLastErrorMessage() ?? "Could not run the native application.");
+
+ // ApplicationRun drains WM_NCDESTROY before returning. If a platform
+ // callback was delivered after the managed owner stopped observing it,
+ // complete the already-observed native teardown milestone here.
+ foreach (IInfiniFrameWindow window in Windows.ToArray())
+ window.Features.Lifecycle.CompleteTeardownAfterNativeLoop();
+ return;
+ }
+
+ foreach (IInfiniFrameWindow window in Windows.ToArray()) window.WaitForClose();
+ }
+
+ private static void EnsureWindowsStaThread() {
+ if (OperatingSystem.IsWindows() && Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
+ throw new InvalidOperationException("InfiniFrameApplication.Run() must be called from a Windows STA thread.");
+ }
+
+ internal void AttachServiceProvider(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;
+ internal IServiceProvider RootServiceProvider => _serviceProvider
+ ?? throw new InvalidOperationException("The application service provider has not been initialized.");
+
+ private void StopRegisteredComponents() {
+ Func[] actions;
+ lock (_gate) {
+ actions = _shutdownActions.ToArray();
+ _shutdownActions.Clear();
+ }
+
+ foreach (Func action in actions) {
+ try { action().GetAwaiter().GetResult(); }
+ catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException) {
+ logger.LogWarning(ex, "Failed to stop an application component.");
+ }
+ }
+ }
+
+ private void StartRegisteredComponents() {
+ Func[] actions;
+ lock (_gate) actions = _startupActions.ToArray();
+ foreach (Func action in actions) action().GetAwaiter().GetResult();
+ }
+
+ private async Task StartRegisteredComponentsAsync() {
+ Func[] actions;
+ lock (_gate) actions = _startupActions.ToArray();
+ foreach (Func action in actions) await action().ConfigureAwait(false);
+ }
+
+ private async Task StopRegisteredComponentsAsync() {
+ Func[] actions;
+ lock (_gate) {
+ actions = _shutdownActions.ToArray();
+ _shutdownActions.Clear();
+ }
+
+ foreach (Func action in actions) {
+ try { await action().ConfigureAwait(false); }
+ catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException) {
+ logger.LogWarning(ex, "Failed to stop an application component.");
+ }
+ }
+ }
+}
diff --git a/src/InfiniFrame.Application/InfiniFrameApplicationBuilder.cs b/src/InfiniFrame.Application/InfiniFrameApplicationBuilder.cs
new file mode 100644
index 000000000..1936a63b8
--- /dev/null
+++ b/src/InfiniFrame.Application/InfiniFrameApplicationBuilder.cs
@@ -0,0 +1,99 @@
+using InfiniFrame.Window;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace InfiniFrame.Application;
+
+/// Composes an InfiniFrame application before it is built.
+public sealed class InfiniFrameApplicationBuilder {
+ private readonly List> _integrations = [];
+ private readonly List<(string? Id, Action Configure)> _windows = [];
+ private string? _webView2RuntimePath;
+ private string? _notificationRegistrationId;
+ private string? _appUserModelId;
+ private string? _defaultNotificationIcon;
+
+ internal InfiniFrameApplicationBuilder(string[]? args) {
+ Args = args ?? [];
+ Services = new ServiceCollection().AddLogging().AddInfiniFrame();
+ }
+
+ internal string[] Args { get; }
+ public IServiceCollection Services { get; }
+
+ public InfiniFrameApplicationBuilder WithWindow(Action configure) {
+ ArgumentNullException.ThrowIfNull(configure);
+ _windows.Add((null, configure));
+ return this;
+ }
+
+ public InfiniFrameApplicationBuilder WithWindow(string id, Action configure) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ ArgumentNullException.ThrowIfNull(configure);
+ if (_windows.Any(window => string.Equals(window.Id, id, StringComparison.Ordinal)))
+ throw new ArgumentException($"A window with id '{id}' is already registered.", nameof(id));
+ _windows.Add((id, configure));
+ return this;
+ }
+
+ public InfiniFrameApplicationBuilder WithWebView2RuntimePath(string path) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(path);
+ _webView2RuntimePath = Path.GetFullPath(path);
+ return this;
+ }
+
+ public InfiniFrameApplicationBuilder WithNotificationRegistrationId(string id) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ _notificationRegistrationId = id;
+ return this;
+ }
+
+ public InfiniFrameApplicationBuilder WithAppUserModelId(string id) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
+ _appUserModelId = id;
+ return this;
+ }
+
+ public InfiniFrameApplicationBuilder WithDefaultNotificationIcon(string path) {
+ ArgumentException.ThrowIfNullOrWhiteSpace(path);
+ _defaultNotificationIcon = Path.GetFullPath(path);
+ return this;
+ }
+
+ internal void AddIntegration(Action integration) {
+ ArgumentNullException.ThrowIfNull(integration);
+ _integrations.Add(integration);
+ }
+
+ public InfiniFrameApplication Build() {
+ InfiniFrameApplication? application = null;
+ IServiceProvider? serviceProvider = null;
+ bool serviceProviderAttached = false;
+ try {
+ application = InfiniFrameApplication.Initialize(new ApplicationConfiguration(
+ _webView2RuntimePath,
+ _notificationRegistrationId,
+ _appUserModelId,
+ _defaultNotificationIcon
+ ));
+
+ Services.AddSingleton(application);
+ serviceProvider = Services.BuildServiceProvider();
+
+ foreach ((string? id, Action configure) in _windows) {
+ if (id is null) application.RegisterWindow(configure);
+ else application.RegisterWindow(id, configure);
+ }
+ application.AttachServiceProvider(serviceProvider);
+ serviceProviderAttached = true;
+ foreach (Action integration in _integrations)
+ integration(application);
+ return application;
+ }
+ catch {
+ application?.Dispose();
+ if (!serviceProviderAttached && serviceProvider is IDisposable disposableServiceProvider)
+ disposableServiceProvider.Dispose();
+ throw;
+ }
+ }
+}
diff --git a/src/InfiniFrame.Blazor/InfiniFrame.Blazor.csproj b/src/InfiniFrame.Blazor/InfiniFrame.Blazor.csproj
index 78d1df882..157d0a13d 100644
--- a/src/InfiniFrame.Blazor/InfiniFrame.Blazor.csproj
+++ b/src/InfiniFrame.Blazor/InfiniFrame.Blazor.csproj
@@ -1,12 +1,10 @@
InfiniLore.InfiniFrame.Blazor
- Library
Pre-built Razor components for custom window chrome in InfiniFrame Blazor applications. Includes drag areas, window buttons, and resize thumbs.
-
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrame.BlazorWebView.csproj b/src/InfiniFrame.BlazorWebView/InfiniFrame.BlazorWebView.csproj
index 3c9df2d66..edce4d8a4 100644
--- a/src/InfiniFrame.BlazorWebView/InfiniFrame.BlazorWebView.csproj
+++ b/src/InfiniFrame.BlazorWebView/InfiniFrame.BlazorWebView.csproj
@@ -1,20 +1,20 @@
InfiniLore.InfiniFrame.BlazorWebView
- Library
Integrates a full Blazor WebAssembly-style application into a native InfiniFrame window with no HTTP server required. The Blazor runtime runs entirely in-process.
-
-
-
-
+
+
-
+
+
+
+
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameApplicationBlazorWebViewExtensions.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameApplicationBlazorWebViewExtensions.cs
new file mode 100644
index 000000000..06686c5eb
--- /dev/null
+++ b/src/InfiniFrame.BlazorWebView/InfiniFrameApplicationBlazorWebViewExtensions.cs
@@ -0,0 +1,227 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using System.Reflection;
+using InfiniFrame.Application;
+using InfiniFrame.BlazorWebView.FileProviders;
+using InfiniFrame.Security;
+using InfiniFrame.StaticAssets;
+using InfiniFrame.Window;
+using InfiniFrame.Window.Features.WebMessaging.Handlers;
+using Microsoft.AspNetCore.Components;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Options;
+
+namespace InfiniFrame.BlazorWebView;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+
+public sealed class InfiniFrameBlazorWebViewConfiguration {
+ private readonly IServiceCollection _services;
+ private readonly List> _windowConfigurations = [];
+
+ internal InfiniFrameBlazorWebViewConfiguration(IServiceCollection services) {
+ _services = services;
+ ConfigureServices();
+ }
+
+ public IInfiniFrameRootComponentList RootComponents { get; } = new InfiniFrameRootComponentList();
+
+ public InfiniFrameBlazorWebViewConfiguration Configure(Action configure) {
+ ArgumentNullException.ThrowIfNull(configure);
+ _services.Configure(configure);
+ return this;
+ }
+
+ public InfiniFrameBlazorWebViewConfiguration ConfigureWindow(Action configure) {
+ ArgumentNullException.ThrowIfNull(configure);
+ _windowConfigurations.Add(configure);
+ return this;
+ }
+
+ internal void AddSingleFileProvider(IFileProvider provider) {
+ ArgumentNullException.ThrowIfNull(provider);
+ _services.AddSingleton(provider);
+ }
+
+ internal void Apply(InfiniFrameApplication application, IReadOnlyList windowIds) {
+ IServiceProvider services = application.RootServiceProvider;
+ var manager = services.GetRequiredService();
+ InfiniFrameBlazorAppConfiguration appConfig = services.GetService>()?.Value
+ ?? new InfiniFrameBlazorAppConfiguration();
+
+ IInfiniFrameJsComponentConfiguration? jsConfiguration =
+ services.GetService();
+ if (jsConfiguration is not null) {
+ application.WindowCreated += _ => {
+ foreach ((Type componentType, string selector) in RootComponents)
+ jsConfiguration.Add(componentType, selector);
+ };
+ }
+
+ IDisposable? exceptionRegistration = TryRegisterUnhandledExceptionHandler(services);
+ application.ApplyWindowIntegration(windowIds, "BlazorWebView", windowBuilder => {
+ foreach (Action configure in _windowConfigurations)
+ configure(windowBuilder);
+ InfiniFrameUriSecurityPolicyRegistry.ConfigureForBuilder(
+ windowBuilder,
+ policyBuilder => policyBuilder.AddTrustedOrigin(appConfig.AppBaseUri));
+ windowBuilder.StaticAssets = services.GetRequiredService().DeepCopy();
+ if (!windowBuilder.EventsStore.CustomScheme.ContainsKey(InfiniFrameWebViewManager.BlazorAppScheme))
+ windowBuilder.RegisterCustomSchemeHandler(InfiniFrameWebViewManager.BlazorAppScheme, manager.HandleWebRequest);
+ windowBuilder.RegisterWebMessageReceivedHandler(manager.HandleWebMessage);
+ windowBuilder.RegisterGetWebMessageHandler();
+ windowBuilder.SetStartPageUrl(BuildStartupUrl(appConfig));
+ });
+ if (exceptionRegistration is not null)
+ application.RegisterShutdownAction(() => {
+ exceptionRegistration.Dispose();
+ return Task.CompletedTask;
+ });
+ }
+
+ private void ConfigureServices() {
+ IFileProvider fileProvider = ConfigureFileProvider(null);
+ _services.AddOptions();
+ _services
+ .AddInfiniFrame()
+ .AddScoped(sp => {
+ var handler = sp.GetRequiredService();
+ Uri appBaseUri = sp.GetRequiredService>().Value.AppBaseUri;
+ return new HttpClient(handler) { BaseAddress = appBaseUri };
+ })
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddBlazorWebView()
+ .AddSingleton(fileProvider)
+ .AddSingleton(static provider => {
+ InfiniFrameBlazorAppConfiguration config = provider.GetService>()?.Value
+ ?? new InfiniFrameBlazorAppConfiguration();
+ return new InfiniFrameStaticAssets {
+ FileProvider = provider.GetRequiredService(),
+ BaseUri = config.AppBaseUri.ToString(),
+ DefaultDocument = NormalizeHostPage(config.HostPage)
+ };
+ })
+ .AddSingleton(RootComponents)
+ .AddSingleton(RootComponents.JSComponents);
+
+ _services.TryAddSingleton();
+ _services.AddInfiniFrameJs();
+ }
+
+ private static IFileProvider ConfigureFileProvider(IFileProvider? fileProvider) {
+ if (fileProvider is not null) return fileProvider;
+ string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
+ var providers = new List();
+ IFileProvider? staticWebAssets = StaticWebAssetsRuntimeFileProvider.TryCreate(baseDirectory, Assembly.GetEntryAssembly());
+ if (staticWebAssets is not null) providers.Add(staticWebAssets);
+ string wwwroot = Path.Join(baseDirectory, "wwwroot");
+ PhysicalFileProvider? physical = Directory.Exists(wwwroot) ? new PhysicalFileProvider(wwwroot) : null;
+ if (physical is not null) providers.Add(physical);
+ return providers.Count switch {
+ 0 => new NullFileProvider(),
+ 1 => providers[0],
+ _ => new DisposableCompositeFileProvider(providers, physical!)
+ };
+ }
+
+ private static string BuildStartupUrl(InfiniFrameBlazorAppConfiguration configuration) {
+ Uri appBaseUri = configuration.AppBaseUri;
+ string hostPage = NormalizeHostPage(configuration.HostPage);
+ return string.Equals(hostPage, "index.html", StringComparison.OrdinalIgnoreCase)
+ ? appBaseUri.ToString()
+ : new Uri(appBaseUri, hostPage).ToString();
+ }
+
+ private static string NormalizeHostPage(string? hostPage) =>
+ !string.IsNullOrWhiteSpace(hostPage) ? hostPage.TrimStart('/') : "index.html";
+
+ private static IDisposable? TryRegisterUnhandledExceptionHandler(IServiceProvider services) {
+ bool enabled = services.GetService>()?.Value
+ .EnableGlobalUnhandledExceptionHandler ?? true;
+ if (!enabled) return null;
+ var source = services.GetRequiredService();
+ return source.Register((_, error) => {
+ try {
+ var window = services.GetService();
+ window?.Invoke(() => window.ShowMessage("Fatal exception", error.ExceptionObject.ToString()));
+ }
+ catch (ObjectDisposedException) { }
+ catch (InvalidOperationException) { }
+ });
+ }
+}
+
+public static class InfiniFrameApplicationBlazorWebViewExtensions {
+ public static InfiniFrameApplicationBuilder WithBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ Action configure
+ ) => builder.UseBlazorWebView(configure);
+
+ public static InfiniFrameApplicationBuilder WithBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ string windowId,
+ Action configure
+ ) => builder.UseBlazorWebView(windowId, configure);
+
+ public static InfiniFrameApplicationBuilder WithBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ IEnumerable windowIds,
+ Action configure
+ ) => builder.UseBlazorWebView(windowIds, configure);
+
+ public static InfiniFrameApplicationBuilder UseBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ Action configure
+ ) => builder.UseBlazorWebView(configure, []);
+
+ public static InfiniFrameApplicationBuilder UseBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ string windowId,
+ Action configure
+ ) => builder.UseBlazorWebView(configure, [windowId]);
+
+ public static InfiniFrameApplicationBuilder UseBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ IEnumerable windowIds,
+ Action configure
+ ) {
+ ArgumentNullException.ThrowIfNull(windowIds);
+ return builder.UseBlazorWebView(configure, windowIds.ToArray());
+ }
+
+ public static InfiniFrameApplicationBuilder UseBlazorWebView(
+ this InfiniFrameApplicationBuilder builder,
+ Action configure,
+ params string[] windowIds
+ ) {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configure);
+ ArgumentNullException.ThrowIfNull(windowIds);
+ if (windowIds.Length > 1)
+ throw new NotSupportedException("BlazorWebView can currently target only one window.");
+
+ var configuration = new InfiniFrameBlazorWebViewConfiguration(builder.Services);
+ configure(configuration);
+ builder.Services.AddSingleton(provider =>
+ ResolveTargetWindow(provider.GetRequiredService(), windowIds));
+ builder.AddIntegration(application => {
+ application.ValidateWindowIntegrationTargets(windowIds, "BlazorWebView");
+ configuration.Apply(application, windowIds);
+ });
+ return builder;
+ }
+
+ private static IInfiniFrameWindow ResolveTargetWindow(IInfiniFrameApplication application, IReadOnlyList windowIds) {
+ if (windowIds.Count > 0) return application.GetWindow(windowIds[0]);
+ if (application.Windows.Count == 1) return application.Windows[0];
+ throw new InvalidOperationException("BlazorWebView could not resolve its target window.");
+ }
+}
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorApp.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorApp.cs
deleted file mode 100644
index 2b64655ec..000000000
--- a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorApp.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-// ---------------------------------------------------------------------------------------------------------------------
-// Imports
-// ---------------------------------------------------------------------------------------------------------------------
-using InfiniFrame.Utilities;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-
-namespace InfiniFrame.BlazorWebView;
-// ---------------------------------------------------------------------------------------------------------------------
-// Code
-// ---------------------------------------------------------------------------------------------------------------------
-///
-/// Represents a running Blazor application hosted in an InfiniFrame native window.
-/// Manages the application lifecycle and service provider.
-///
-public class InfiniFrameBlazorApp(
- IServiceProvider provider,
- IInfiniFrameRootComponentList rootComponents,
- IInfiniFrameJsComponentConfiguration? rootComponentConfiguration = null,
- IDisposable? unhandledExceptionRegistration = null
-) : IInfiniFrameBlazorApp {
-
- private int _disposed;
- ///
- /// Gets the service provider for the running Blazor application.
- ///
- public IServiceProvider ServiceProvider { get; } = provider;
- private IInfiniFrameRootComponentList RootComponents { get; } = rootComponents;
- private IInfiniFrameJsComponentConfiguration? RootComponentConfiguration { get; } = rootComponentConfiguration;
- private IDisposable? UnhandledExceptionRegistration { get; } = unhandledExceptionRegistration;
-
- // -----------------------------------------------------------------------------------------------------------------
- // Methods
- // -----------------------------------------------------------------------------------------------------------------
- ///
- public async Task RunAsync(CancellationToken ct = default) {
- ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
-
- var window = ServiceProvider.GetRequiredService();
-
- RegisterRootComponents();
-
- try {
- await window.WaitForCloseAsync(ct).ConfigureAwait(false);
- }
- finally {
- await DisposeAsync().ConfigureAwait(false);
- }
- }
-
- ///
- ///
- /// This method uses synchronous-over-async patterns for disposal. It should only be called
- /// from threads without a SynchronizationContext. Prefer for async contexts.
- ///
- public void Run() {
- ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
-
- if (SynchronizationContext.Current is not null) {
- throw new InvalidOperationException(
- "Run() must be called from a thread without a SynchronizationContext to avoid deadlock during disposal. " +
- "Use RunAsync() instead.");
- }
-
- var window = ServiceProvider.GetRequiredService();
-
- RegisterRootComponents();
-
- try {
- window.WaitForClose();
- }
- finally {
- DisposeAsync().AsTask().GetAwaiter().GetResult();
- }
- }
-
- ///
- /// Asynchronously disposes of the application and its service provider.
- ///
- ///
- /// This method uses best-effort disposal: exceptions thrown during service provider
- /// disposal are caught and logged but do not propagate to the caller. This prevents
- /// resource cleanup failures from masking the original application shutdown.
- ///
- public async ValueTask DisposeAsync() {
- if (Interlocked.Exchange(ref _disposed, 1) != 0) return;
-
- ILogger? logger = null;
-
- try {
- logger = ServiceProvider.GetService>();
-
- UnhandledExceptionRegistration?.Dispose();
-
- switch (ServiceProvider) {
- case IAsyncDisposable asyncDisposable:
- await asyncDisposable.DisposeAsync().ConfigureAwait(false);
- break;
-
- case IDisposable disposable:
- disposable.Dispose();
- break;
- }
- }
- catch (Exception e) when (ExceptionsUtility.IsNonFatalException(e)) {
- logger?.LogError(e, "Error disposing of InfiniFrameBlazorApp");
- }
- }
-
- private void RegisterRootComponents() {
- if (RootComponentConfiguration is null) return;
-
- foreach ((Type, string) component in RootComponents) {
- RootComponentConfiguration.Add(component.Item1, component.Item2);
- }
- }
-}
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppBuilder.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppBuilder.cs
deleted file mode 100644
index b892c038f..000000000
--- a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppBuilder.cs
+++ /dev/null
@@ -1,241 +0,0 @@
-// ---------------------------------------------------------------------------------------------------------------------
-// Imports
-// ---------------------------------------------------------------------------------------------------------------------
-using System.Reflection;
-using InfiniFrame.BlazorWebView.FileProviders;
-using InfiniFrame.Security;
-using InfiniFrame.StaticAssets;
-using Microsoft.AspNetCore.Components;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.FileProviders;
-using Microsoft.Extensions.Options;
-
-namespace InfiniFrame.BlazorWebView;
-// ---------------------------------------------------------------------------------------------------------------------
-// Code
-// ---------------------------------------------------------------------------------------------------------------------
-///
-/// Builder for creating a Blazor application hosted in an InfiniFrame native window.
-///
-public class InfiniFrameBlazorAppBuilder : IInfiniFrameBlazorAppBuilder {
-
- // -----------------------------------------------------------------------------------------------------------------
- // Constructors
- // -----------------------------------------------------------------------------------------------------------------
- private InfiniFrameBlazorAppBuilder() {}
- ///
- public IInfiniFrameRootComponentList RootComponents { get; } = new InfiniFrameRootComponentList();
- ///
- public IServiceCollection Services { get; } = new ServiceCollection();
- ///
- public IInfiniFrameWindowBuilder WindowBuilder { get; } = InfiniFrameWindowBuilder.Create();
-
- ///
- /// Creates a default builder with standard configuration, command-line args, and window builder action.
- ///
- /// Optional command-line arguments.
- /// An optional action to configure the window builder.
- /// A new instance.
- public static InfiniFrameBlazorAppBuilder CreateDefault(
- string[]? args = null,
- Action? windowBuilder = null
- )
- => CreateDefault(null, args, windowBuilder);
-
- ///
- /// Creates a default builder with standard configuration and command-line args.
- ///
- /// An optional file provider for static assets.
- /// Optional command-line arguments.
- /// An optional action to configure the window builder.
- /// A new instance.
- public static InfiniFrameBlazorAppBuilder CreateDefault(IFileProvider? fileProvider, string[]? args = null, Action? windowBuilder = null) {
- // We don't use the args for anything right now, but we want to accept them
- // here so that it shows up this way in the project templates.
- var appBuilder = new InfiniFrameBlazorAppBuilder();
- IFileProvider resolvedFileProvider = ConfigureFileProvider(fileProvider);
-
- appBuilder.Services.AddOptions();
-
- appBuilder.Services
- .AddInfiniFrame()
- .AddScoped(static sp => {
- var handler = sp.GetRequiredService();
- return new HttpClient(handler) { BaseAddress = new Uri(InfiniFrameWebViewManager.AppBaseUri) };
- })
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddSingleton(static provider => provider.GetRequiredService().Build(provider))
- .AddBlazorWebView()
- .AddSingleton(resolvedFileProvider)
- .AddSingleton(static provider => {
- InfiniFrameBlazorAppConfiguration config = provider.GetService>()?.Value
- ?? new InfiniFrameBlazorAppConfiguration();
-
- return new InfiniFrameStaticAssets {
- FileProvider = provider.GetRequiredService(),
- BaseUri = config.AppBaseUri.ToString(),
- DefaultDocument = NormalizeHostPage(config.HostPage)
- };
- })
- .AddSingleton(appBuilder.WindowBuilder)
- .AddSingleton(appBuilder.RootComponents)
- .AddSingleton(appBuilder.RootComponents.JSComponents);
-
- appBuilder.Services.TryAddSingleton();
-
- appBuilder.Services.AddInfiniFrameJs();
- appBuilder.WindowBuilder.RegisterGetWebMessageHandler();
-
- windowBuilder?.Invoke(appBuilder.WindowBuilder);
-
- return appBuilder;
- }
-
- ///
- /// Configures the file provider to be used by the application.
- /// If a custom is provided, that instance will be used.
- /// Otherwise, a default provider will be configured based on the application's "wwwroot" directory.
- ///
- ///
- /// An optional instance.
- ///
- ///
- /// An instance of that represents either the specified file provider
- /// or the default provider if none is supplied.
- ///
- private static IFileProvider ConfigureFileProvider(IFileProvider? fileProvider) {
- if (fileProvider is not null) return fileProvider;
-
- string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
-
- var providers = new List();
-
- IFileProvider? staticWebAssetsProvider = StaticWebAssetsRuntimeFileProvider.TryCreate(baseDirectory, Assembly.GetEntryAssembly());
- if (staticWebAssetsProvider is not null) providers.Add(staticWebAssetsProvider);
-
- string defaultWwwrootPath = Path.Join(baseDirectory, "wwwroot");
- bool hasPhysicalWwwroot = Directory.Exists(defaultWwwrootPath);
- PhysicalFileProvider? physicalWwwrootProvider = hasPhysicalWwwroot
- ? new PhysicalFileProvider(defaultWwwrootPath)
- : null;
- if (physicalWwwrootProvider is not null) providers.Add(physicalWwwrootProvider);
-
- return providers.Count switch {
- 0 => new NullFileProvider(),
- 1 => providers[0],
- _ => new DisposableCompositeFileProvider(providers, physicalWwwrootProvider!)
- };
-
- }
-
- ///
- /// Configures the InfiniFrame window builder action.
- ///
- /// The action to configure the window builder.
- /// The for chaining.
- public InfiniFrameBlazorAppBuilder WithInfiniFrameWindowBuilder(Action windowBuilder) {
- windowBuilder.Invoke(WindowBuilder);
- return this;
- }
-
- ///
- /// Builds a new using a service provider created from .
- ///
- /// A newly created .
- public InfiniFrameBlazorApp Build()
- => Build(Services.BuildServiceProvider());
-
- ///
- /// Builds a new using an externally supplied .
- ///
- ///
- /// The pre-built service provider to use for resolving all application services.
- /// Ownership is transferred to the returned app instance; when that app is disposed, this provider is disposed if it
- /// implements
- /// or . Do not dispose the same provider separately.
- ///
- /// A newly created .
- ///
- /// Calling this method more than once on the same builder instance is not supported. Each call mutates builder state
- /// (for example, by registering additional scheme handlers), which can lead to duplicate registrations.
- /// Create a new builder for each app instance.
- ///
- public InfiniFrameBlazorApp Build(IServiceProvider serviceProvider) {
- ArgumentNullException.ThrowIfNull(serviceProvider);
-
- var manager = serviceProvider.GetRequiredService();
- InfiniFrameBlazorAppConfiguration appConfig = serviceProvider.GetService>()?.Value
- ?? new InfiniFrameBlazorAppConfiguration();
- InfiniFrameUriSecurityPolicyRegistry.ConfigureForBuilder(
- WindowBuilder,
- configure: policyBuilder => policyBuilder.AddTrustedOrigin(appConfig.AppBaseUri));
- string startupUrl = BuildStartupUrl(appConfig);
- var staticAssets = serviceProvider.GetRequiredService();
-
- WindowBuilder.StaticAssets = staticAssets.DeepCopy();
-
- if (!WindowBuilder.EventsStore.CustomScheme.ContainsKey(InfiniFrameWebViewManager.BlazorAppScheme)) {
- WindowBuilder.RegisterCustomSchemeHandler(InfiniFrameWebViewManager.BlazorAppScheme, manager.HandleWebRequest);
- }
-
- WindowBuilder.SetStartPageUrl(startupUrl);
-
- IDisposable? unhandledExceptionRegistration = TryRegisterUnhandledExceptionHandler(serviceProvider);
-
- return new InfiniFrameBlazorApp(
- serviceProvider,
- serviceProvider.GetRequiredService(),
- serviceProvider.GetService(),
- unhandledExceptionRegistration
- );
- }
-
- private static string BuildStartupUrl(InfiniFrameBlazorAppConfiguration configuration) {
- Uri appBaseUri = configuration.AppBaseUri;
- string hostPage = NormalizeHostPage(configuration.HostPage);
-
- return string.Equals(hostPage, "index.html", StringComparison.OrdinalIgnoreCase)
- ? appBaseUri.ToString()
- : new Uri(appBaseUri, hostPage).ToString();
- }
-
- private static string NormalizeHostPage(string? hostPage)
- => !string.IsNullOrWhiteSpace(hostPage)
- ? hostPage.TrimStart('/')
- : "index.html";
-
- private static IDisposable? TryRegisterUnhandledExceptionHandler(IServiceProvider serviceProvider) {
- bool enableGlobalUnhandledExceptionHandler = serviceProvider.GetService>()?
- .Value.EnableGlobalUnhandledExceptionHandler ?? true;
-
- if (!enableGlobalUnhandledExceptionHandler) return null;
-
- var exceptionSource = serviceProvider.GetRequiredService();
-
- return exceptionSource.Register((_, error) => {
- try {
- var window = serviceProvider.GetService();
-
- // Only interact if safe
- window?.Invoke(() => {
- window.ShowMessage(
- "Fatal exception",
- error.ExceptionObject.ToString()
- );
- });
- }
- catch (ObjectDisposedException) {
- // Window already closed; nothing to report.
- }
- catch (InvalidOperationException) {
- // Service not available; nothing to report.
- }
- });
- }
-}
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppConfiguration.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppConfiguration.cs
index 23923fe1c..8447ec080 100644
--- a/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppConfiguration.cs
+++ b/src/InfiniFrame.BlazorWebView/InfiniFrameBlazorAppConfiguration.cs
@@ -34,8 +34,6 @@ public class InfiniFrameBlazorAppConfiguration {
///
/// Gets or sets how outbound messages are handled when is reached.
/// The default rejects the new message, which provides immediate backpressure to the non-awaitable Blazor API.
- /// Note: The current implementation always uses TryWrite (non-blocking), so this setting only controls
- /// diagnostic logging and is reserved for future use with blocking write paths.
///
public BoundedChannelFullMode WebMessageQueueFullMode { get; set; } = BoundedChannelFullMode.DropWrite;
}
diff --git a/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs b/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs
index 7dd3033ba..be14683e8 100644
--- a/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs
+++ b/src/InfiniFrame.BlazorWebView/InfiniFrameWebViewManager.cs
@@ -40,7 +40,8 @@ public class InfiniFrameWebViewManager : WebViewManager, IInfiniFrameWebViewMana
private readonly Task _messagePumpTask;
private readonly int _messageQueueCapacity;
private readonly BoundedChannelFullMode _messageQueueFullMode;
- private readonly IInfiniFrameUriSecurityPolicy _uriSecurityPolicy;
+ private readonly Uri _appBaseUri;
+ private readonly IInfiniFrameUriSecurityPolicy _fallbackUriSecurityPolicy;
private int _disposeStarted;
private int _disposed;
@@ -50,7 +51,6 @@ public class InfiniFrameWebViewManager : WebViewManager, IInfiniFrameWebViewMana
///
/// Initializes a new instance of the class.
///
- /// The window builder for configuring the native window.
/// The service provider for dependency injection.
/// The Blazor dispatcher for thread marshalling.
/// The file provider for serving static assets.
@@ -58,7 +58,6 @@ public class InfiniFrameWebViewManager : WebViewManager, IInfiniFrameWebViewMana
/// The Blazor application configuration.
/// The logger
public InfiniFrameWebViewManager(
- IInfiniFrameWindowBuilder builder,
IServiceProvider provider,
Dispatcher dispatcher,
IFileProvider fileProvider,
@@ -69,6 +68,13 @@ ILogger logger
: base(provider, dispatcher, config.Value.AppBaseUri, fileProvider, jsComponents, config.Value.HostPage) {
_logger = logger;
InfiniFrameBlazorAppConfiguration configuration = config.Value;
+ if (configuration.AppBaseUri is null
+ || !configuration.AppBaseUri.IsAbsoluteUri
+ || string.IsNullOrWhiteSpace(configuration.AppBaseUri.Scheme)
+ || string.IsNullOrWhiteSpace(configuration.AppBaseUri.Host)) {
+ throw new ArgumentException("AppBaseUri must be an absolute URI with a scheme and host.", nameof(configuration.AppBaseUri));
+ }
+ _appBaseUri = configuration.AppBaseUri;
if (configuration.WebMessageQueueCapacity <= 0) {
throw new ArgumentOutOfRangeException(
nameof(configuration.WebMessageQueueCapacity),
@@ -76,34 +82,25 @@ ILogger logger
"The WebView message queue capacity must be positive.");
}
+ // TryWrite cannot report which item DropWrite discarded. Use Wait so the non-awaitable
+ // producer receives a false result and the loss is observable through diagnostics.
+ BoundedChannelFullMode effectiveFullMode = configuration.WebMessageQueueFullMode == BoundedChannelFullMode.DropWrite
+ ? BoundedChannelFullMode.Wait
+ : configuration.WebMessageQueueFullMode;
_channel = Channel.CreateBounded(new BoundedChannelOptions(configuration.WebMessageQueueCapacity) {
SingleReader = true,
SingleWriter = false,
- FullMode = configuration.WebMessageQueueFullMode,
+ FullMode = effectiveFullMode,
AllowSynchronousContinuations = false
});
_messageQueueCapacity = configuration.WebMessageQueueCapacity;
- _messageQueueFullMode = configuration.WebMessageQueueFullMode;
- _uriSecurityPolicy = InfiniFrameUriSecurityPolicyRegistry
- .GetForBuilder(builder)
+ _messageQueueFullMode = effectiveFullMode;
+ _fallbackUriSecurityPolicy = InfiniFrameUriSecurityPolicy.Default
.WithTrustedOrigin(configuration.AppBaseUri);
// ReSharper disable once ConvertClosureToMethodGroup
LazyWindow = new Lazy(() => provider.GetRequiredService());
- builder.RegisterWebMessageReceivedHandler((_, message, origin) => {
- if (IsDisposingOrDisposed) return;
-
- _logger.LogTrace("Web message callback received from native. Origin: {Origin}, Length: {Length}", origin, message.Length);
-
- try {
- HandleWebMessage((message, origin));
- }
- catch (Exception ex) when (ExceptionsUtility.IsNonFatalException(ex)) {
- _logger.LogWarning(ex, "Unhandled exception while handling native web message callback.");
- }
- });
-
_messagePumpTask = MessagePump();
_logger.LogDebug(
"Started WebView message pump. QueueCapacity: {QueueCapacity}, FullMode: {FullMode}",
@@ -135,7 +132,8 @@ ILogger logger
return default;
}
- if (!_uriSecurityPolicy.IsNavigationSchemeAllowed(requestUri.Scheme)) {
+ IInfiniFrameUriSecurityPolicy uriSecurityPolicy = GetUriSecurityPolicy(infiniFrameWindow);
+ if (!uriSecurityPolicy.IsNavigationSchemeAllowed(requestUri.Scheme)) {
_logger.LogWarning(
"Rejected web request due to disallowed URI scheme. Scheme: {Scheme}, Url: {Url}",
requestUri.Scheme,
@@ -143,11 +141,11 @@ ILogger logger
return default;
}
- if (!_uriSecurityPolicy.IsTrustedOrigin(requestUri)) {
+ if (!uriSecurityPolicy.IsTrustedOrigin(requestUri)) {
_logger.LogWarning(
"Rejected web request due to untrusted origin. RequestOrigin: {RequestOrigin}, TrustedOrigins: {TrustedOrigins}",
requestUri,
- _uriSecurityPolicy.TrustedOrigins);
+ uriSecurityPolicy.TrustedOrigins);
return default;
}
@@ -183,20 +181,38 @@ ILogger logger
// -----------------------------------------------------------------------------------------------------------------
// Web message handling
// -----------------------------------------------------------------------------------------------------------------
- private void HandleWebMessage((string Message, string? Origin) state) {
+ ///
+ public void HandleWebMessage(IInfiniFrameWindow window, string message, string? origin) {
+ ArgumentNullException.ThrowIfNull(window);
+ ArgumentNullException.ThrowIfNull(message);
+
+ if (IsDisposingOrDisposed) return;
+
+ _logger.LogTrace("Web message callback received from native. Origin: {Origin}, Length: {Length}", origin, message.Length);
+
+ try {
+ HandleWebMessageCore(window, message, origin);
+ }
+ catch (Exception ex) when (ExceptionsUtility.IsNonFatalException(ex)) {
+ _logger.LogWarning(ex, "Unhandled exception while handling native web message callback.");
+ }
+ }
+
+ private void HandleWebMessageCore(IInfiniFrameWindow window, string message, string? origin) {
if (IsDisposingOrDisposed) return;
Uri? messageOriginUrl;
- if (!string.IsNullOrWhiteSpace(state.Origin)) {
- if (!Uri.TryCreate(state.Origin, UriKind.Absolute, out messageOriginUrl)) {
+ if (!string.IsNullOrWhiteSpace(origin)) {
+ if (!Uri.TryCreate(origin, UriKind.Absolute, out messageOriginUrl)) {
_logger.LogWarning(
"Rejected web message because origin parsing failed. Origin: {Origin}",
- state.Origin);
+ origin);
return;
}
}
- else if (Uri.TryCreate(AppBaseUri, UriKind.Absolute, out Uri? fallback)) {
+ else if (_appBaseUri.IsAbsoluteUri) {
+ Uri fallback = _appBaseUri;
messageOriginUrl = fallback;
_logger.LogDebug(
@@ -209,11 +225,12 @@ private void HandleWebMessage((string Message, string? Origin) state) {
return;
}
- if (!_uriSecurityPolicy.IsTrustedOrigin(messageOriginUrl)) {
+ IInfiniFrameUriSecurityPolicy uriSecurityPolicy = GetUriSecurityPolicy(window);
+ if (!uriSecurityPolicy.IsTrustedOrigin(messageOriginUrl)) {
_logger.LogWarning(
"Rejected web message due to origin mismatch. Origin: {MessageOrigin}, TrustedOrigins: {TrustedOrigins}",
messageOriginUrl,
- _uriSecurityPolicy.TrustedOrigins);
+ uriSecurityPolicy.TrustedOrigins);
return;
}
@@ -221,9 +238,14 @@ private void HandleWebMessage((string Message, string? Origin) state) {
// messages because the pump synchronously invokes that same thread to send responses.
if (IsDisposingOrDisposed) return;
- MessageReceived(messageOriginUrl, state.Message);
+ MessageReceived(messageOriginUrl, message);
}
+ private IInfiniFrameUriSecurityPolicy GetUriSecurityPolicy(IInfiniFrameWindow? window) =>
+ window is null
+ ? _fallbackUriSecurityPolicy
+ : InfiniFrameUriSecurityPolicyRegistry.GetForWindow(window);
+
// -----------------------------------------------------------------------------------------------------------------
// Navigation
// -----------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.Js/InfiniFrame.Js.csproj b/src/InfiniFrame.Js/InfiniFrame.Js.csproj
index f114d9cbe..90834938e 100644
--- a/src/InfiniFrame.Js/InfiniFrame.Js.csproj
+++ b/src/InfiniFrame.Js/InfiniFrame.Js.csproj
@@ -2,9 +2,6 @@
InfiniLore.InfiniFrame.Js
- Library
-
- true
JavaScript interop utilities for InfiniFrame Blazor applications. Provides pointer capture helpers and built-in window management message handlers.
$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/../..'))
@@ -15,6 +12,12 @@
$(MSBuildProjectDirectory)/obj/frontend-build.stamp
+
+
+
+
+
+
@@ -67,16 +67,16 @@
+ Exclude="Native/build/**/*;Native/packages/**/*;Native/src/Embedded/InfiniFrameJs/**/*" />
+ Exclude="Native/build/**/*;Native/packages/**/*;Native/src/Embedded/InfiniFrameJs/**/*" />
+ Exclude="Native/build/**/*;Native/packages/**/*;Native/src/Embedded/InfiniFrameJs/**/*" />
+ Exclude="Native/build/**/*;Native/packages/**/*" />
+ Exclude="Native/build/**/*;Native/packages/**/*" />
diff --git a/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj b/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj
index e129f9500..296b9a934 100644
--- a/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj
+++ b/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj
@@ -2,15 +2,15 @@
InfiniLore.InfiniFrame.NativeBridge
- Library
true
- true
true
C++ native bridge layer for InfiniFrame. Provides the P/Invoke interop between .NET and the native window implementations (WebView2, WebKitGTK, WKWebView).
+
+
diff --git a/src/InfiniFrame.NativeBridge/Managed/Handles/NativeApplicationHandle.cs b/src/InfiniFrame.NativeBridge/Managed/Handles/NativeApplicationHandle.cs
new file mode 100644
index 000000000..ad6defa6a
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Managed/Handles/NativeApplicationHandle.cs
@@ -0,0 +1,13 @@
+using Microsoft.Win32.SafeHandles;
+
+namespace InfiniFrame.NativeBridge.Handles;
+
+/// Owns a native InfiniFrame application instance.
+public sealed class NativeApplicationHandle : SafeHandleZeroOrMinusOneIsInvalid {
+ internal NativeApplicationHandle(IntPtr handle) : base(true) => SetHandle(handle);
+
+ protected override bool ReleaseHandle() {
+ InfiniFrameNativeInteropStatus status = InfiniFrameNative.ApplicationDestructor(handle);
+ return status == InfiniFrameNativeInteropStatus.Success;
+ }
+}
diff --git a/src/InfiniFrame.NativeBridge/Managed/NativeApi/Exports/InfiniFrameNative.Lifecycle.cs b/src/InfiniFrame.NativeBridge/Managed/NativeApi/Exports/InfiniFrameNative.Lifecycle.cs
index d1176d5c3..d373e9bda 100644
--- a/src/InfiniFrame.NativeBridge/Managed/NativeApi/Exports/InfiniFrameNative.Lifecycle.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/NativeApi/Exports/InfiniFrameNative.Lifecycle.cs
@@ -3,13 +3,60 @@
// ---------------------------------------------------------------------------------------------------------------------
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using InfiniFrame.NativeBridge.Parameters;
+using InfiniFrame.NativeBridge.Parameters.Application;
+using InfiniFrame.NativeBridge.Parameters.Window;
namespace InfiniFrame.NativeBridge;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
public partial class InfiniFrameNative {
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_ctor", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus ApplicationConstructorNative(out IntPtr value);
+
+ internal static InfiniFrameNativeInteropStatus ApplicationConstructor(out IntPtr value)
+ => ApplicationConstructorNative(out value);
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_Register", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ internal static partial InfiniFrameNativeInteropStatus ApplicationRegister(IntPtr instance);
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_Configure", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus ApplicationConfigureNative(IntPtr instance, IntPtr parameters);
+
+ internal static InfiniFrameNativeInteropStatus ApplicationConfigure(
+ IntPtr instance,
+ in InfiniFrameNativeApplicationParameters parameters
+ ) {
+ var marshaller = new InfiniFrameNativeApplicationParametersMarshaller.ManagedToUnmanagedIn();
+ marshaller.FromManaged(parameters);
+ IntPtr unmanagedPtr = IntPtr.Zero;
+
+ try {
+ unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
+ Marshal.StructureToPtr(marshaller.ToUnmanaged(), unmanagedPtr, false);
+ return ApplicationConfigureNative(instance, unmanagedPtr);
+ }
+ finally {
+ if (unmanagedPtr != IntPtr.Zero) Marshal.FreeHGlobal(unmanagedPtr);
+ marshaller.Free();
+ }
+ }
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_Run", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ internal static partial InfiniFrameNativeInteropStatus ApplicationRun(IntPtr instance);
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_Shutdown", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ internal static partial InfiniFrameNativeInteropStatus ApplicationShutdown(IntPtr instance);
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeApplication_dtor")]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ internal static partial InfiniFrameNativeInteropStatus ApplicationDestructor(IntPtr instance);
+
///
/// Creates a new native window instance with the specified parameters.
///
@@ -21,16 +68,16 @@ public partial class InfiniFrameNative {
private static partial InfiniFrameNativeInteropStatus ConstructorNative(IntPtr parameters, out IntPtr value);
internal static InfiniFrameNativeInteropStatus Constructor(
- in InfiniFrameNativeParameters parameters,
+ in InfiniFrameNativeWindowParameters parameters,
out IntPtr value
) {
- var marshaller = new InfiniFrameNativeParametersMarshaller.ManagedToUnmanagedIn();
+ var marshaller = new InfiniFrameNativeWindowParametersMarshaller.ManagedToUnmanagedIn();
marshaller.FromManaged(parameters);
var unmanaged = marshaller.ToUnmanaged();
IntPtr unmanagedPtr = IntPtr.Zero;
try {
- unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
+ unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
Marshal.StructureToPtr(unmanaged, unmanagedPtr, false);
return ConstructorNative(unmanagedPtr, out value);
}
@@ -71,6 +118,10 @@ out IntPtr value
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial InfiniFrameNativeInteropStatus SetReadyCallback(IntPtr instance, ContextAction callback, IntPtr context);
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNative_SetReadyFailureCallback", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ internal static partial InfiniFrameNativeInteropStatus SetReadyFailureCallback(IntPtr instance, ContextAction callback, IntPtr context);
+
[LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNative_SetTeardownCallback", SetLastError = true)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial InfiniFrameNativeInteropStatus SetTeardownCallback(IntPtr instance, ContextAction callback, IntPtr context);
diff --git a/src/InfiniFrame.NativeBridge/Managed/NativeApi/Testing/InfiniFrameNative.Testing.cs b/src/InfiniFrame.NativeBridge/Managed/NativeApi/Testing/InfiniFrameNative.Testing.cs
index 708f605f4..cf09c89a1 100644
--- a/src/InfiniFrame.NativeBridge/Managed/NativeApi/Testing/InfiniFrameNative.Testing.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/NativeApi/Testing/InfiniFrameNative.Testing.cs
@@ -3,7 +3,8 @@
// ---------------------------------------------------------------------------------------------------------------------
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using InfiniFrame.NativeBridge.Parameters;
+using InfiniFrame.NativeBridge.Parameters.Application;
+using InfiniFrame.NativeBridge.Parameters.Window;
namespace InfiniFrame.NativeBridge;
// ---------------------------------------------------------------------------------------------------------------------
@@ -62,21 +63,21 @@ out int valid
private static partial InfiniFrameNativeInteropStatus IsColorSchemeChangeNative(IntPtr lParam, out int result);
///
- /// Returns a native pointer to a newly allocated InfiniFrameInitParams clone.
+ /// Returns a native pointer to a newly allocated InfiniFrameWindowInitParams clone.
/// Ownership is transferred to managed caller, which must call exactly once.
///
/// The parameters to clone.
/// The native pointer to the cloned parameters.
/// A status code indicating success or failure.
- internal static InfiniFrameNativeInteropStatus NativeParametersReturnAsIsPtr(ref InfiniFrameNativeParameters parameters, out IntPtr newParametersPtr) {
- var marshaller = new InfiniFrameNativeParametersMarshaller.ManagedToUnmanagedIn();
+ internal static InfiniFrameNativeInteropStatus NativeParametersReturnAsIsPtr(ref InfiniFrameNativeWindowParameters parameters, out IntPtr newParametersPtr) {
+ var marshaller = new InfiniFrameNativeWindowParametersMarshaller.ManagedToUnmanagedIn();
marshaller.FromManaged(parameters);
var unmanaged = marshaller.ToUnmanaged();
InfiniFrameNativeInteropStatus status;
IntPtr unmanagedPtr = IntPtr.Zero;
try {
- unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
+ unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
Marshal.StructureToPtr(unmanaged, unmanagedPtr, false);
status = NativeParametersReturnAsIsNative(unmanagedPtr, out newParametersPtr);
}
@@ -91,6 +92,41 @@ internal static InfiniFrameNativeInteropStatus NativeParametersReturnAsIsPtr(ref
return status;
}
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeTests_NativeApplicationParametersReturnAsIs", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus NativeApplicationParametersReturnAsIsNative(IntPtr parameters, out IntPtr newParameters);
+
+ internal static InfiniFrameNativeInteropStatus NativeApplicationParametersReturnAsIsPtr(
+ ref InfiniFrameNativeApplicationParameters parameters,
+ out IntPtr newParametersPtr
+ ) {
+ var marshaller = new InfiniFrameNativeApplicationParametersMarshaller.ManagedToUnmanagedIn();
+ marshaller.FromManaged(parameters);
+ var unmanaged = marshaller.ToUnmanaged();
+ InfiniFrameNativeInteropStatus status;
+ IntPtr unmanagedPtr = IntPtr.Zero;
+
+ try {
+ unmanagedPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
+ Marshal.StructureToPtr(unmanaged, unmanagedPtr, false);
+ status = NativeApplicationParametersReturnAsIsNative(unmanagedPtr, out newParametersPtr);
+ }
+ finally {
+ if (unmanagedPtr != IntPtr.Zero) Marshal.FreeHGlobal(unmanagedPtr);
+ marshaller.Free();
+ }
+
+ if (newParametersPtr == IntPtr.Zero) throw new InvalidOperationException("Native function returned null pointer");
+ return status;
+ }
+
+ internal static InfiniFrameNativeInteropStatus FreeApplicationParameters(IntPtr parameters)
+ => FreeApplicationParametersNative(parameters);
+
+ [LibraryImport(ArtifactManifest.NativeLibraryName, EntryPoint = "InfiniFrameNativeTests_FreeApplicationParameters", SetLastError = true)]
+ [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus FreeApplicationParametersNative(IntPtr parameters);
+
///
/// Frees init parameters that were allocated by native code during testing.
///
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParameters.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParameters.cs
new file mode 100644
index 000000000..324ff7dd5
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParameters.cs
@@ -0,0 +1,29 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using System.Runtime.InteropServices;
+
+namespace InfiniFrame.NativeBridge.Parameters.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+///
+/// Process-wide configuration passed to the native InfiniFrame application.
+/// Field order is part of the managed/native ABI. Append fields before only.
+///
+[StructLayout(LayoutKind.Sequential)]
+public struct InfiniFrameNativeApplicationParameters() {
+ [MarshalAs(UnmanagedType.LPUTF8Str)]
+ internal string? WebView2RuntimePath;
+
+ [MarshalAs(UnmanagedType.LPUTF8Str)]
+ internal string? NotificationRegistrationId;
+
+ [MarshalAs(UnmanagedType.LPUTF8Str)]
+ internal string? AppUserModelId;
+
+ [MarshalAs(UnmanagedType.LPUTF8Str)]
+ internal string? DefaultNotificationIcon;
+
+ internal readonly int Size = Marshal.SizeOf();
+}
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersEqualityComparer.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersEqualityComparer.cs
new file mode 100644
index 000000000..48e995cc8
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersEqualityComparer.cs
@@ -0,0 +1,34 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+namespace InfiniFrame.NativeBridge.Parameters.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+///
+/// Compares native application parameters by their configuration values and ABI size.
+///
+internal sealed class InfiniFrameNativeApplicationParametersEqualityComparer
+ : IEqualityComparer {
+ internal static readonly InfiniFrameNativeApplicationParametersEqualityComparer Instance = new();
+
+ private InfiniFrameNativeApplicationParametersEqualityComparer() {}
+
+ public bool Equals(
+ InfiniFrameNativeApplicationParameters x,
+ InfiniFrameNativeApplicationParameters y
+ ) => x.WebView2RuntimePath == y.WebView2RuntimePath
+ && x.NotificationRegistrationId == y.NotificationRegistrationId
+ && x.AppUserModelId == y.AppUserModelId
+ && x.DefaultNotificationIcon == y.DefaultNotificationIcon
+ && x.Size == y.Size;
+
+ public int GetHashCode(InfiniFrameNativeApplicationParameters obj)
+ => HashCode.Combine(
+ obj.WebView2RuntimePath,
+ obj.NotificationRegistrationId,
+ obj.AppUserModelId,
+ obj.DefaultNotificationIcon,
+ obj.Size
+ );
+}
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersMarshaller.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersMarshaller.cs
new file mode 100644
index 000000000..f14ee006b
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersMarshaller.cs
@@ -0,0 +1,52 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace InfiniFrame.NativeBridge.Parameters.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+[CustomMarshaller(
+ typeof(InfiniFrameNativeApplicationParameters),
+ MarshalMode.ManagedToUnmanagedIn,
+ typeof(ManagedToUnmanagedIn)
+)]
+internal static class InfiniFrameNativeApplicationParametersMarshaller {
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct Unmanaged {
+ internal IntPtr WebView2RuntimePath;
+ internal IntPtr NotificationRegistrationId;
+ internal IntPtr AppUserModelId;
+ internal IntPtr DefaultNotificationIcon;
+ internal int Size;
+ }
+
+ internal ref struct ManagedToUnmanagedIn {
+ private Unmanaged _unmanaged;
+
+ public void FromManaged(InfiniFrameNativeApplicationParameters managed) {
+ _unmanaged = new Unmanaged {
+ WebView2RuntimePath = ToUtf8Ptr(managed.WebView2RuntimePath),
+ NotificationRegistrationId = ToUtf8Ptr(managed.NotificationRegistrationId),
+ AppUserModelId = ToUtf8Ptr(managed.AppUserModelId),
+ DefaultNotificationIcon = ToUtf8Ptr(managed.DefaultNotificationIcon),
+ Size = managed.Size
+ };
+ }
+
+ public Unmanaged ToUnmanaged() => _unmanaged;
+
+ public void Free() {
+ Marshal.FreeCoTaskMem(_unmanaged.WebView2RuntimePath);
+ Marshal.FreeCoTaskMem(_unmanaged.NotificationRegistrationId);
+ Marshal.FreeCoTaskMem(_unmanaged.AppUserModelId);
+ Marshal.FreeCoTaskMem(_unmanaged.DefaultNotificationIcon);
+ }
+
+ private static IntPtr ToUtf8Ptr(string? value) => value is null
+ ? IntPtr.Zero
+ : Marshal.StringToCoTaskMemUTF8(value);
+ }
+}
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersValidator.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersValidator.cs
new file mode 100644
index 000000000..28f94b782
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Application/InfiniFrameNativeApplicationParametersValidator.cs
@@ -0,0 +1,20 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+using System.Runtime.InteropServices;
+using FluentValidation;
+
+namespace InfiniFrame.NativeBridge.Parameters.Application;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+///
+/// Validates instances.
+///
+public sealed class InfiniFrameNativeApplicationParametersValidator
+ : AbstractValidator {
+ public InfiniFrameNativeApplicationParametersValidator() {
+ RuleFor(parameters => parameters.Size)
+ .Equal(Marshal.SizeOf());
+ }
+}
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/CustomSchemeNameMemory.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/CustomSchemeNameMemory.cs
similarity index 98%
rename from src/InfiniFrame.NativeBridge/Managed/Parameters/CustomSchemeNameMemory.cs
rename to src/InfiniFrame.NativeBridge/Managed/Parameters/Window/CustomSchemeNameMemory.cs
index 84123a977..124840b18 100644
--- a/src/InfiniFrame.NativeBridge/Managed/Parameters/CustomSchemeNameMemory.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/CustomSchemeNameMemory.cs
@@ -3,7 +3,7 @@
// ---------------------------------------------------------------------------------------------------------------------
using System.Runtime.InteropServices;
-namespace InfiniFrame.NativeBridge.Parameters;
+namespace InfiniFrame.NativeBridge.Parameters.Window;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParameters.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParameters.cs
similarity index 95%
rename from src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParameters.cs
rename to src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParameters.cs
index 2c20201e5..ec6a6fdda 100644
--- a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParameters.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParameters.cs
@@ -4,7 +4,7 @@
using System.Runtime.InteropServices;
using InfiniFrame.NativeBridge.Delegates;
-namespace InfiniFrame.NativeBridge.Parameters;
+namespace InfiniFrame.NativeBridge.Parameters.Window;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -16,7 +16,7 @@ namespace InfiniFrame.NativeBridge.Parameters;
/// Passed to the native layer as a sequentially laid-out struct.
///
[StructLayout(LayoutKind.Sequential)]
-public struct InfiniFrameNativeParameters() {
+public struct InfiniFrameNativeWindowParameters() {
// Content strings
///
@@ -70,20 +70,6 @@ public struct InfiniFrameNativeParameters() {
[MarshalAs(UnmanagedType.LPUTF8Str)]
internal string? BrowserControlInitParameters;
- ///
- /// WINDOWS ONLY: OPTIONAL: Path to an extracted fixed-version WebView2 runtime used when the window is created.
- ///
- [MarshalAs(UnmanagedType.LPUTF8Str)]
- internal string? WebView2RuntimePath;
-
- ///WINDOWS: OPTIONAL: Registers the application for toast notifications. If not provided, use Window Title.
- [MarshalAs(UnmanagedType.LPUTF8Str)]
- internal string? NotificationRegistrationId;
-
- ///WINDOWS: OPTIONAL: Explicit application identity used by the taskbar for grouping and pinning.
- [MarshalAs(UnmanagedType.LPUTF8Str)]
- internal string? WindowsAppUserModelId;
-
///
/// OPTIONAL: Default icon path applied to notifications when IconPath is not specified.
///
@@ -103,6 +89,9 @@ public struct InfiniFrameNativeParameters() {
///
internal IntPtr NativeParent;
+ /// Process-scoped application owner for this native window.
+ internal IntPtr ApplicationInstance;
+
// Event callbacks
///Set by InfiniFrameOptionsBuilder
@@ -401,5 +390,5 @@ public struct InfiniFrameNativeParameters() {
/// construction.
///
[MarshalAs(UnmanagedType.I4)]
- internal readonly int Size = Marshal.SizeOf();
+ internal readonly int Size = Marshal.SizeOf();
}
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersEqualityComparer.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersEqualityComparer.cs
similarity index 88%
rename from src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersEqualityComparer.cs
rename to src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersEqualityComparer.cs
index a2efd00cb..9d821d1fc 100644
--- a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersEqualityComparer.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersEqualityComparer.cs
@@ -1,32 +1,32 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-namespace InfiniFrame.NativeBridge.Parameters;
+namespace InfiniFrame.NativeBridge.Parameters.Window;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
///
-/// Compares two instances for value equality,
+/// Compares two instances for value equality,
/// ignoring callback handler fields. This comparer is intended for parameter-change
/// detection where two instances with different callbacks but identical configuration
/// values are considered equivalent.
///
-internal sealed class InfiniFrameNativeParametersEqualityComparer : IEqualityComparer {
+internal sealed class InfiniFrameNativeWindowParametersEqualityComparer : IEqualityComparer {
///
/// Singleton instance of the equality comparer.
///
- internal static readonly InfiniFrameNativeParametersEqualityComparer Instance = new();
+ internal static readonly InfiniFrameNativeWindowParametersEqualityComparer Instance = new();
- private InfiniFrameNativeParametersEqualityComparer() {}
+ private InfiniFrameNativeWindowParametersEqualityComparer() {}
///
- /// Determines whether two instances are equal
+/// Determines whether two instances are equal
/// by comparing all value fields.
///
/// The first instance.
/// The second instance.
/// true if the instances are equal; otherwise, false.
- public bool Equals(InfiniFrameNativeParameters x, InfiniFrameNativeParameters y) {
+ public bool Equals(InfiniFrameNativeWindowParameters x, InfiniFrameNativeWindowParameters y) {
// Handlers are not checked because they are set by the constructor and are not user-configurable.
// x.ClosingHandler == y.ClosingHandler
// && x.ClosedHandler == y.ClosedHandler
@@ -51,9 +51,6 @@ public bool Equals(InfiniFrameNativeParameters x, InfiniFrameNativeParameters y)
if (x.TemporaryFilesPath != y.TemporaryFilesPath) return false;
if (x.UserAgent != y.UserAgent) return false;
if (x.BrowserControlInitParameters != y.BrowserControlInitParameters) return false;
- if (x.WebView2RuntimePath != y.WebView2RuntimePath) return false;
- if (x.NotificationRegistrationId != y.NotificationRegistrationId) return false;
- if (x.WindowsAppUserModelId != y.WindowsAppUserModelId) return false;
if (x.DefaultNotificationIcon != y.DefaultNotificationIcon) return false;
// Runtime configuration
@@ -126,12 +123,12 @@ public bool Equals(InfiniFrameNativeParameters x, InfiniFrameNativeParameters y)
}
///
- /// Returns a hash code for the specified instance
+/// Returns a hash code for the specified instance
/// based on its value fields.
///
/// The instance to hash.
/// A hash code value.
- public int GetHashCode(InfiniFrameNativeParameters obj) {
+ public int GetHashCode(InfiniFrameNativeWindowParameters obj) {
var hashCode = new HashCode();
// Content strings
@@ -144,9 +141,6 @@ public int GetHashCode(InfiniFrameNativeParameters obj) {
hashCode.Add(obj.TemporaryFilesPath);
hashCode.Add(obj.UserAgent);
hashCode.Add(obj.BrowserControlInitParameters);
- hashCode.Add(obj.WebView2RuntimePath);
- hashCode.Add(obj.NotificationRegistrationId);
- hashCode.Add(obj.WindowsAppUserModelId);
hashCode.Add(obj.DefaultNotificationIcon);
// Runtime configuration
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersMarshaller.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersMarshaller.cs
similarity index 93%
rename from src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersMarshaller.cs
rename to src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersMarshaller.cs
index 07bd05afe..4d86a6589 100644
--- a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersMarshaller.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersMarshaller.cs
@@ -6,20 +6,20 @@
using System.Runtime.InteropServices.Marshalling;
using InfiniFrame.NativeBridge.Delegates;
-namespace InfiniFrame.NativeBridge.Parameters;
+namespace InfiniFrame.NativeBridge.Parameters.Window;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
///
-/// Custom marshaller for converting
+/// Custom marshaller for converting
/// to an unmanaged representation for native interop calls.
///
[CustomMarshaller(
- typeof(InfiniFrameNativeParameters),
+ typeof(InfiniFrameNativeWindowParameters),
MarshalMode.ManagedToUnmanagedIn,
typeof(ManagedToUnmanagedIn)
)]
-internal static class InfiniFrameNativeParametersMarshaller {
+internal static class InfiniFrameNativeWindowParametersMarshaller {
// -----------------------------------------------------------------------------------------------------------------
// Methods
@@ -71,8 +71,8 @@ private static IntPtr GetCustomSchemeName(IntPtr[]? values, int index)
=> values is not null && values.Length > index ? values[index] : IntPtr.Zero;
///
- /// Unmanaged layout of used for native interop.
- /// Field order must match the C++ InfiniFrameInitParams struct exactly.
+/// Unmanaged layout of used for native interop.
+/// Field order must match the C++ InfiniFrameWindowInitParams struct exactly.
///
[StructLayout(LayoutKind.Sequential)]
internal struct Unmanaged {
@@ -86,9 +86,6 @@ internal struct Unmanaged {
internal IntPtr TemporaryFilesPath;
internal IntPtr UserAgent;
internal IntPtr BrowserControlInitParameters;
- internal IntPtr WebView2RuntimePath;
- internal IntPtr NotificationRegistrationId;
- internal IntPtr WindowsAppUserModelId;
internal IntPtr DefaultNotificationIcon;
// Runtime configuration
@@ -96,6 +93,7 @@ internal struct Unmanaged {
// Parent window
internal IntPtr NativeParent;
+ internal IntPtr ApplicationInstance;
// Event callbacks
internal IntPtr ClosingHandler;
@@ -186,7 +184,7 @@ internal struct Unmanaged {
}
///
- /// Marshals managed to the native layout.
+/// Marshals managed to the native layout.
///
[SuppressMessage("ReSharper", "NotAccessedField.Local")]
internal ref struct ManagedToUnmanagedIn {
@@ -212,7 +210,7 @@ internal ref struct ManagedToUnmanagedIn {
/// Copies all values from the managed source into the unmanaged representation.
///
/// The managed parameters source.
- public void FromManaged(InfiniFrameNativeParameters managed) {
+ public void FromManaged(InfiniFrameNativeWindowParameters managed) {
// Retain delegate references to prevent GC during native constructor call.
_closingHandler = managed.ClosingHandler;
_closedHandler = managed.ClosedHandler;
@@ -240,9 +238,6 @@ public void FromManaged(InfiniFrameNativeParameters managed) {
TemporaryFilesPath = ToUtf8Ptr(managed.TemporaryFilesPath),
UserAgent = ToUtf8Ptr(managed.UserAgent),
BrowserControlInitParameters = ToUtf8Ptr(managed.BrowserControlInitParameters),
- WebView2RuntimePath = ToUtf8Ptr(managed.WebView2RuntimePath),
- NotificationRegistrationId = ToUtf8Ptr(managed.NotificationRegistrationId),
- WindowsAppUserModelId = ToUtf8Ptr(managed.WindowsAppUserModelId),
DefaultNotificationIcon = ToUtf8Ptr(managed.DefaultNotificationIcon),
// Runtime configuration
@@ -250,6 +245,7 @@ public void FromManaged(InfiniFrameNativeParameters managed) {
// Parent window
NativeParent = managed.NativeParent,
+ ApplicationInstance = managed.ApplicationInstance,
// Event callbacks
ClosingHandler = ToFunctionPtr(managed.ClosingHandler),
@@ -359,9 +355,6 @@ public void Free() {
Marshal.FreeCoTaskMem(_unmanaged.TemporaryFilesPath);
Marshal.FreeCoTaskMem(_unmanaged.UserAgent);
Marshal.FreeCoTaskMem(_unmanaged.BrowserControlInitParameters);
- Marshal.FreeCoTaskMem(_unmanaged.WebView2RuntimePath);
- Marshal.FreeCoTaskMem(_unmanaged.NotificationRegistrationId);
- Marshal.FreeCoTaskMem(_unmanaged.WindowsAppUserModelId);
Marshal.FreeCoTaskMem(_unmanaged.DefaultNotificationIcon);
Marshal.FreeCoTaskMem(_unmanaged.MenuBarJson);
diff --git a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersValidator.cs b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersValidator.cs
similarity index 89%
rename from src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersValidator.cs
rename to src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersValidator.cs
index 5eeacce35..8efa85ae0 100644
--- a/src/InfiniFrame.NativeBridge/Managed/Parameters/InfiniFrameNativeParametersValidator.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/Parameters/Window/InfiniFrameNativeWindowParametersValidator.cs
@@ -4,26 +4,26 @@
using System.Runtime.InteropServices;
using FluentValidation;
-namespace InfiniFrame.NativeBridge.Parameters;
+namespace InfiniFrame.NativeBridge.Parameters.Window;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
///
-/// Validates instances using FluentValidation rules.
+/// Validates instances using FluentValidation rules.
///
-public sealed class InfiniFrameNativeParametersValidator
- : AbstractValidator {
+public sealed class InfiniFrameNativeWindowParametersValidator
+ : AbstractValidator {
// -----------------------------------------------------------------------------------------------------------------
// Constructors
// -----------------------------------------------------------------------------------------------------------------
///
- /// Initializes a new instance of
+/// Initializes a new instance of
/// and configures all validation rules.
///
- public InfiniFrameNativeParametersValidator() {
+ public InfiniFrameNativeWindowParametersValidator() {
RuleFor(p => p.Size)
- .Equal(Marshal.SizeOf());
+ .Equal(Marshal.SizeOf());
RuleFor(p => p)
.Must(p =>
@@ -87,12 +87,6 @@ public InfiniFrameNativeParametersValidator() {
.NotNull().WithMessage("CustomSchemeNames must be specified.")
.Must(names => names.Length <= 16).WithMessage("CustomSchemeNames must contain at most 16 names.");
- RuleFor(p => p.WindowsAppUserModelId)
- .NotEmpty()
- .MaximumLength(128)
- .Must(value => value is null || !value.Any(char.IsWhiteSpace))
- .When(p => p.WindowsAppUserModelId is not null)
- .WithMessage("WindowsAppUserModelId must contain 1 to 128 characters and cannot contain whitespace.");
}
// -----------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.Shared/Utilities/UnixPInvoke.cs b/src/InfiniFrame.NativeBridge/Managed/UnixPInvoke.cs
similarity index 84%
rename from src/InfiniFrame.Shared/Utilities/UnixPInvoke.cs
rename to src/InfiniFrame.NativeBridge/Managed/UnixPInvoke.cs
index cf991d63d..c438aebeb 100644
--- a/src/InfiniFrame.Shared/Utilities/UnixPInvoke.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/UnixPInvoke.cs
@@ -1,9 +1,9 @@
-// ---------------------------------------------------------------------------------------------------------------------
+// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
using System.Runtime.InteropServices;
-namespace InfiniFrame.Utilities;
+namespace InfiniFrame.NativeBridge;
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt b/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt
index 6ab5b1c82..99436664c 100644
--- a/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt
+++ b/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt
@@ -40,32 +40,34 @@ infiniframe_setup_dependencies()
# ----------------------------------------------------------------------------------------------------------------------
set(COMMON_SOURCES
src/Embedded/Embedded.cpp
- src/Api/Utilities/ExportErrorState.cpp
- src/Runtime/Shared/Window/WindowEvents.cpp
- src/Runtime/Shared/Window/WindowState.cpp
- src/Runtime/Shared/Operations/NativeOperation.cpp
- src/Runtime/Shared/Operations/DialogOperation.cpp
- src/Api/Exports/Exports.CustomSchemes.cpp
- src/Api/Exports/Exports.Dispatch.cpp
- src/Api/Exports/Exports.Dialog.cpp
- src/Api/Exports/Exports.Events.cpp
- src/Api/Exports/Exports.Lifecycle.cpp
- src/Api/Exports/Exports.Memory.cpp
- src/Api/Exports/Exports.Monitors.cpp
- src/Api/Exports/Exports.Platform.Linux.cpp
- src/Api/Exports/Exports.Platform.MacOs.cpp
- src/Api/Exports/Exports.Platform.Windows.cpp
- src/Api/Exports/Exports.Window.Actions.cpp
- src/Api/Exports/Exports.Window.Getters.cpp
- src/Api/Exports/Exports.Window.Navigation.cpp
- src/Api/Exports/Exports.Window.Setters.cpp
- src/Api/Exports/Exports.Window.Taskbar.cpp
- src/Api/Exports/Exports.Menu.cpp
+ src/Runtime/Internal/Interop/Exports/ExportErrorState.cpp
+ src/Runtime/Internal/Application/InfiniFrameApplication.cpp
+ src/Runtime/Internal/Window/WindowEvents.cpp
+ src/Runtime/Internal/Window/WindowState.cpp
+ src/Runtime/Internal/Operations/NativeOperation.cpp
+ src/Runtime/Internal/Operations/DialogOperation.cpp
+ src/Abi/Exports.CustomSchemes.cpp
+ src/Abi/Exports.Dispatch.cpp
+ src/Abi/Exports.Dialog.cpp
+ src/Abi/Exports.Events.cpp
+ src/Abi/Exports.Lifecycle.cpp
+ src/Abi/Exports.Memory.cpp
+ src/Abi/Exports.Monitors.cpp
+ src/Abi/Exports.Platform.Linux.cpp
+ src/Abi/Exports.Platform.MacOs.cpp
+ src/Abi/Exports.Platform.Windows.cpp
+ src/Abi/Exports.Window.Actions.cpp
+ src/Abi/Exports.Window.Getters.cpp
+ src/Abi/Exports.Window.Navigation.cpp
+ src/Abi/Exports.Window.Setters.cpp
+ src/Abi/Exports.Window.Taskbar.cpp
+ src/Abi/Exports.Menu.cpp
)
set(TEST_SOURCES
- src/Api/Testing/Exports.Tests.cpp
- src/Api/Testing/Exports.CustomSchemeResponseTests.cpp
+ src/Abi/Testing/Exports.Tests.cpp
+ src/Abi/Testing/Exports.CustomSchemeResponseTests.cpp
+ tests/PublicAbiCompileBoundary.cpp
)
set(WINDOWS_SOURCES
@@ -163,30 +165,31 @@ set(MAC_SOURCES
set(HEADER_FILES
src/Embedded/Embedded.h
- src/Runtime/Shared/WebView/CustomSchemeResponse.h
- src/Runtime/Shared/Operations/NativeOperation.h
- src/Runtime/Shared/Operations/NavigationOperation.h
- src/Api/Exports/Exports.h
- src/Api/Utilities/ExportErrorState.h
- src/Api/Utilities/ExportExecution.h
- src/Api/Utilities/ExportStringHelpers.h
- src/Api/Utilities/ExportValidation.h
- src/Api/Utilities/Utilities.h
- src/Runtime/Shared/Window/InfiniFrame.h
- src/Runtime/Shared/Window/InfiniFrameDialog.h
- src/Runtime/Shared/Window/InfiniFrameInitParams.h
- src/Runtime/Shared/Window/InfiniFrameWindow.h
- src/Runtime/Shared/Types/Basic.h
- src/Runtime/Shared/Types/Callbacks.h
- src/Runtime/Shared/Types/DialogButtons.h
- src/Runtime/Shared/Types/DialogIcon.h
- src/Runtime/Shared/Types/DialogResult.h
- src/Runtime/Shared/Types/Monitor.h
- src/Runtime/Shared/Utilities/Dimensions.h
- src/Runtime/Shared/Utilities/ErrorCode.h
- src/Runtime/Shared/Utilities/InteropStatus.h
- src/Runtime/Shared/Utilities/StringArrayCopy.h
- src/Runtime/Shared/Utilities/StringCopy.h
+ src/Runtime/Internal/WebView/CustomSchemeResponse.h
+ src/Runtime/Internal/Operations/NativeOperation.h
+ src/Runtime/Internal/Operations/NavigationOperation.h
+ src/Abi/Exports.h
+ src/Runtime/Internal/Interop/Exports/ExportErrorState.h
+ src/Runtime/Internal/Interop/Exports/ExportExecution.h
+ src/Runtime/Internal/Interop/Exports/ExportStringHelpers.h
+ src/Runtime/Internal/Interop/Exports/ExportValidation.h
+ src/Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h
+ src/Runtime/Internal/Interop/Types/InteropStatus.h
+ src/Runtime/Internal/Window/InfiniFrameDialog.h
+ src/Runtime/Internal/Interop/Types/InfiniFrameWindowInitParams.h
+ src/Runtime/Internal/Interop/Types/InfiniFrameWindow.h
+ src/Runtime/Internal/Application/InfiniFrameApplication.h
+ src/Runtime/Internal/Interop/Types/Basic.h
+ src/Runtime/Internal/Interop/Types/Callbacks.h
+ src/Runtime/Internal/Interop/Types/CustomSchemeResponse.h
+ src/Runtime/Internal/Interop/Types/DialogButtons.h
+ src/Runtime/Internal/Interop/Types/DialogIcon.h
+ src/Runtime/Internal/Interop/Types/DialogResult.h
+ src/Runtime/Internal/Interop/Types/Monitor.h
+ src/Runtime/Internal/Utilities/Dimensions.h
+ src/Runtime/Internal/Utilities/ErrorCode.h
+ src/Runtime/Internal/Utilities/StringArrayCopy.h
+ src/Runtime/Internal/Utilities/StringCopy.h
)
if (WIN32)
diff --git a/src/InfiniFrame.NativeBridge/Native/README.md b/src/InfiniFrame.NativeBridge/Native/README.md
index 896725ce6..19356478a 100644
--- a/src/InfiniFrame.NativeBridge/Native/README.md
+++ b/src/InfiniFrame.NativeBridge/Native/README.md
@@ -10,8 +10,7 @@ Native/
BUILDING.md # Build performance tips
src/
- Api/
- Exports/ # extern "C" functions (the public C ABI)
+ Abi/ # extern "C" functions (the public C ABI)
Exports.Window.Actions.cpp # Center, Restore, Focus, Notifications
Exports.Window.Getters.cpp # Query window state (size, position, flags)
Exports.Window.Setters.cpp # Modify window state
@@ -28,30 +27,15 @@ Native/
Exports.Platform.Windows.cpp # Windows-specific exports
Exports.Platform.MacOs.cpp # macOS-specific exports
Exports.Platform.Linux.cpp # Linux-specific exports
- Testing/ # Test-only exports
- Utilities/ # Export infrastructure (validation, error state, string helpers)
-
- Runtime/
- Shared/ # Cross-platform runtime code
- Window/ # Window state, events, configuration
- Operations/ # Async operation infrastructure
- Platform/ # Platform detection
+ Runtime/Internal/ # Internal runtime, interop types, operations, and window state
+ Runtime/Platform/ # Windows, Linux, and macOS implementations
Embedded/ # Embedded JS assets
-
- Platforms/
- Windows/ # WebView2 implementation
- Linux/ # WebKitGTK implementation
- MacOs/ # WKWebView implementation
-
- include/
- InfiniFrameWindow.h # Main public header InfiniFrameWindow class
- Types/ # Shared ABI types (enums, structs)
```
## Public C API
-The public API is defined in `src/Api/Exports/` and consists of `extern "C"` functions with the prefix `InfiniFrameNative_`. These functions are called by the .NET managed layer via P/Invoke.
+The public API is defined in `src/Abi/` and consists of `extern "C"` functions with the prefix `InfiniFrameNative_`. These functions are called by the .NET managed layer via P/Invoke.
### String Ownership
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.CustomSchemes.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.CustomSchemes.cpp
similarity index 96%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.CustomSchemes.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.CustomSchemes.cpp
index d5c91a815..61099f026 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.CustomSchemes.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.CustomSchemes.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -19,4 +19,4 @@ EXPORTED InteropStatus InfiniFrameNative_AddCustomSchemeName(InfiniFrameWindow*
window->AddCustomSchemeName(scheme);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dialog.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dialog.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dialog.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dialog.cpp
index 10cfa862c..20d072af9 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dialog.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dialog.cpp
@@ -1,7 +1,8 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
+#include "Runtime/Internal/Window/InfiniFrameDialog.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -274,4 +275,4 @@ InfiniFrameNative_CancelDialog(InfiniFrameWindow* instance, const uint64_t opera
*cancelled = window->CancelDialog(operationId);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dispatch.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dispatch.cpp
similarity index 97%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dispatch.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dispatch.cpp
index cfcacfa2e..4cabbe803 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Dispatch.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Dispatch.cpp
@@ -1,8 +1,8 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
-#include "Runtime/Shared/Operations/NativeOperation.h"
+#include "Abi/Exports.h"
+#include "Runtime/Internal/Operations/NativeOperation.h"
#ifdef __linux__
#include "Runtime/Platform/Linux/Core/UiThread.Gtk.h"
#endif
@@ -87,4 +87,4 @@ EXPORTED InteropStatus InfiniFrameNative_CancelOperation(
window->CancelOperation(operationId, static_cast(result));
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Events.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Events.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Events.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Events.cpp
index 02cdbd338..4d3088c88 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Events.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Events.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -103,4 +103,4 @@ EXPORTED InteropStatus InfiniFrameNative_SetDragDropEnabled(InfiniFrameWindow* i
window->SetDragDropEnabled(enabled != 0);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Lifecycle.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Lifecycle.cpp
similarity index 53%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Lifecycle.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Lifecycle.cpp
index 73b6c49e6..2d71bef1c 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Lifecycle.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Lifecycle.cpp
@@ -1,7 +1,9 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
+#include "Runtime/Internal/Interop/Types/InfiniFrameWindowInitParams.h"
+#include "Runtime/Internal/Application/InfiniFrameApplication.h"
#ifdef __linux__
#include "Runtime/Platform/Linux/Core/UiThread.Gtk.h"
#endif
@@ -9,11 +11,89 @@
// Code
// ---------------------------------------------------------------------------------------------------------------------
extern "C" {
+/// @brief Creates a native application instance.
+EXPORTED InteropStatus InfiniFrameNativeApplication_ctor(InfiniFrameApplication** value) {
+ ResetOut(value, static_cast(nullptr));
+ return RunExportStatus(
+ [&] {
+ if (!EnsureOutNotNull(value, "value")) return;
+ auto instance = std::make_unique();
+ *value = instance.release();
+ });
+}
+
+/// @brief Registers process-wide native application state.
+EXPORTED InteropStatus InfiniFrameNativeApplication_Register(InfiniFrameApplication* instance) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance")) return;
+ instance->Register();
+ });
+}
+
+EXPORTED InteropStatus InfiniFrameNativeApplication_Configure(
+ InfiniFrameApplication* instance,
+ InfiniFrameApplicationInitParams* parameters
+) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance")) return;
+ if (!EnsureNotNull(parameters, "parameters")) return;
+ if (parameters->StructSize != static_cast(sizeof(InfiniFrameApplicationInitParams)))
+ throw std::invalid_argument("InfiniFrameApplicationInitParams.Size does not match native struct size.");
+ instance->Configure(*parameters);
+ });
+}
+
+/// @brief Runs the native application loop.
+EXPORTED InteropStatus InfiniFrameNativeApplication_Run(InfiniFrameApplication* instance) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance")) return;
+ instance->Run();
+ });
+}
+
+/// @brief Requests native application shutdown.
+EXPORTED InteropStatus InfiniFrameNativeApplication_Shutdown(InfiniFrameApplication* instance) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance")) return;
+ instance->Shutdown();
+ });
+}
+
+/// @brief Destroys a native application instance.
+EXPORTED InteropStatus InfiniFrameNativeApplication_dtor(InfiniFrameApplication* instance) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance")) return;
+#ifdef _WIN32
+ if (instance->GetWindowCount() != 0)
+ throw std::runtime_error("Cannot destroy a native application while windows are still alive.");
+#endif
+ std::unique_ptr guard{instance};
+ });
+}
+
+/// @brief Returns the number of native windows tracked by the application.
+EXPORTED InteropStatus InfiniFrameNativeApplication_GetWindowCount(
+ InfiniFrameApplication* instance,
+ std::size_t* value
+) {
+ ResetOut(value, static_cast(0));
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(instance, "instance") || !EnsureOutNotNull(value, "value")) return;
+ *value = instance->GetWindowCount();
+ });
+}
+
/// @brief Creates a new native window with the given parameters.
/// @param initParams Initialization parameters for the window.
/// @param[out] value Receives the newly created window handle.
/// @return InteropStatus
-EXPORTED InteropStatus InfiniFrameNative_ctor(InfiniFrameInitParams* initParams, InfiniFrameWindow** value) {
+EXPORTED InteropStatus InfiniFrameNative_ctor(InfiniFrameWindowInitParams* initParams, InfiniFrameWindow** value) {
ResetOut(value, static_cast(nullptr));
return RunExportStatus(
[&] {
@@ -21,8 +101,8 @@ EXPORTED InteropStatus InfiniFrameNative_ctor(InfiniFrameInitParams* initParams,
return;
if (initParams == nullptr)
throw std::invalid_argument("Argument 'initParams' is null.");
- if (initParams->StructSize != static_cast(sizeof(InfiniFrameInitParams))) {
- throw std::invalid_argument("InfiniFrameInitParams.Size does not match native struct size.");
+ if (initParams->StructSize != static_cast(sizeof(InfiniFrameWindowInitParams))) {
+ throw std::invalid_argument("InfiniFrameWindowInitParams.Size does not match native struct size.");
}
auto instance = std::make_unique(initParams);
*value = instance.release();
@@ -86,6 +166,19 @@ EXPORTED InteropStatus InfiniFrameNative_SetReadyCallback(
});
}
+EXPORTED InteropStatus InfiniFrameNative_SetReadyFailureCallback(
+ InfiniFrameWindow* instance,
+ const ContextAction callback,
+ void* context
+ ) {
+ return RunWindowExportStatus(
+ instance, [&](InfiniFrameWindow* window) {
+ if (callback == nullptr)
+ throw std::invalid_argument("Argument 'callback' is null.");
+ window->SetReadyFailureCallback(callback, context);
+ });
+}
+
/// @brief Registers a callback for when teardown begins.
/// @param instance The window handle.
/// @param callback Context action invoked when teardown starts.
@@ -114,4 +207,4 @@ EXPORTED InteropStatus InfiniFrameNative_Shutdown() {
});
}
#endif
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Memory.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Memory.cpp
similarity index 98%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Memory.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Memory.cpp
index 273f8e961..6c4bcf844 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Memory.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Memory.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -51,4 +51,4 @@ EXPORTED InteropStatus InfiniFrameNative_GetLastErrorMessage(const char** value)
*value = GetLastErrorMessageCopy();
return InteropStatus::Success;
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Menu.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Menu.cpp
similarity index 98%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Menu.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Menu.cpp
index 9bfcc3d62..8cdf5886b 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Menu.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Menu.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -57,4 +57,4 @@ EXPORTED InteropStatus InfiniFrameNative_ClickMenuItem(InfiniFrameWindow* instan
window->ClickMenuItemById(menuItemId);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Monitors.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Monitors.cpp
similarity index 97%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Monitors.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Monitors.cpp
index 11a4c2525..bf3579900 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Monitors.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Monitors.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -20,4 +20,4 @@ EXPORTED InteropStatus InfiniFrameNative_GetAllMonitors(
window->GetAllMonitors(callback);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Linux.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Linux.cpp
similarity index 96%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Linux.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Linux.cpp
index 5e36b5b19..fe4c74b75 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Linux.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Linux.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -21,4 +21,4 @@ EXPORTED InteropStatus InfiniFrameNative_getGtkWindow_linux(InfiniFrameWindow* i
});
}
#endif
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.MacOs.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.MacOs.cpp
similarity index 97%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.MacOs.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.MacOs.cpp
index 885b695fe..5b392ba02 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.MacOs.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.MacOs.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -31,4 +31,4 @@ EXPORTED InteropStatus InfiniFrameNative_getNSWindow_mac(InfiniFrameWindow* inst
});
}
#endif
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Windows.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Windows.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Windows.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Windows.cpp
index b0dfc3b6c..182861895 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Platform.Windows.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Platform.Windows.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
#ifdef _WIN32
#include "Runtime/Platform/Windows/Window.Win32.Context.h"
#endif
@@ -85,4 +85,4 @@ EXPORTED InteropStatus InfiniFrameNative_getWebView2RuntimeVersion_win32(const c
});
}
#endif
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Actions.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Actions.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Actions.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Actions.cpp
index 379dd47f0..d791cb8ff 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Actions.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Actions.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -133,4 +133,4 @@ EXPORTED InteropStatus InfiniFrameNative_CancelNotification(
window->CancelNotification(operationId, canceled);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Getters.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Getters.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Getters.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Getters.cpp
index 395f695e1..e1d426806 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Getters.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Getters.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -445,4 +445,4 @@ EXPORTED InteropStatus InfiniFrameNative_GetIconFileName(InfiniFrameWindow* inst
*value = window->GetIconFileName();
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Navigation.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Navigation.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Navigation.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Navigation.cpp
index 80c5765a9..9cb23788c 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Navigation.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Navigation.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -113,4 +113,4 @@ EXPORTED InteropStatus InfiniFrameNative_GetCurrentUrl(InfiniFrameWindow* instan
*value = window->GetCurrentUrl();
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Setters.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Setters.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Setters.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Setters.cpp
index 7dde8b3d7..569f5ce55 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Setters.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Setters.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -248,4 +248,4 @@ EXPORTED InteropStatus InfiniFrameNative_SetZoom(InfiniFrameWindow* instance, co
window->SetZoom(zoom);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Taskbar.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Taskbar.cpp
similarity index 98%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Taskbar.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Taskbar.cpp
index c01f6ce6e..3ebc50bf2 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.Window.Taskbar.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.Window.Taskbar.cpp
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Api/Exports/Exports.h"
+#include "Abi/Exports.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -76,4 +76,4 @@ EXPORTED InteropStatus InfiniFrameNative_GetTaskbarProgressSupported(
window->GetTaskbarProgressSupported(supported);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.h b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.h
similarity index 64%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.h
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.h
index f274d916b..3b5ae0a3e 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Exports/Exports.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Exports.h
@@ -14,9 +14,24 @@
#define EXPORTED
#endif
-#include "Runtime/Shared/Types/Basic.h"
-#include "Runtime/Shared/Utilities/InteropStatus.h"
-#include "Api/Utilities/Utilities.h"
+#include "Runtime/Internal/Interop/Types/Basic.h"
+#include "Runtime/Internal/Interop/Types/InteropStatus.h"
+#include "Runtime/Internal/Interop/Exports/ExportErrorState.h"
+#include "Runtime/Internal/Interop/Exports/ExportExecution.h"
+#include "Runtime/Internal/Interop/Exports/ExportStringHelpers.h"
+#include "Runtime/Internal/Interop/Exports/ExportValidation.h"
+
+using infiniframe::exports::EnsureNotNull;
+using infiniframe::exports::EnsureOutNotNull;
+using infiniframe::exports::GetLastErrorMessageCopy;
+using infiniframe::exports::ResetOut;
+using infiniframe::exports::ResetOut2;
+using infiniframe::exports::RunExportStatus;
+using infiniframe::exports::RunReturnExport;
+using infiniframe::exports::RunWindowExportStatus;
+using infiniframe::exports::RunWindowReturnExport;
+using infiniframe::exports::NullToEmpty;
+using infiniframe::exports::DuplicateString;
// ---------------------------------------------------------------------------------------------------------------------
// String Ownership Contract
@@ -36,4 +51,4 @@
//
// NULL semantics:
// Returning nullptr from an owned-string function means "no value" (e.g. no
-// file selected). The caller must still check before calling FreeString.
\ No newline at end of file
+// file selected). The caller must still check before calling FreeString.
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.CustomSchemeResponseTests.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.CustomSchemeResponseTests.cpp
similarity index 96%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.CustomSchemeResponseTests.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.CustomSchemeResponseTests.cpp
index 1bee06f24..9490b4084 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.CustomSchemeResponseTests.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.CustomSchemeResponseTests.cpp
@@ -1,9 +1,9 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Runtime/Shared/Window/InfiniFrame.h"
-#include "Api/Exports/Exports.h"
-#include "Runtime/Shared/WebView/CustomSchemeResponse.h"
+#include "Abi/Exports.h"
+#include "Runtime/Internal/WebView/CustomSchemeResponse.h"
+#include "Runtime/Internal/Utilities/StringCopy.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -116,4 +116,4 @@ EXPORTED InteropStatus InfiniFrameNativeTests_BuildHeaders(
}
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.Tests.cpp b/src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.Tests.cpp
similarity index 83%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.Tests.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.Tests.cpp
index 064d30c9f..0d33185cd 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Testing/Exports.Tests.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Abi/Testing/Exports.Tests.cpp
@@ -1,9 +1,10 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#include "Runtime/Shared/Window/InfiniFrame.h"
-#include "Api/Exports/Exports.h"
-#include "Runtime/Shared/WebView/CustomSchemeResponse.h"
+#include "Runtime/Internal/Interop/Types/InfiniFrameWindowInitParams.h"
+#include "Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h"
+#include "Abi/Exports.h"
+#include "Runtime/Internal/WebView/CustomSchemeResponse.h"
#ifdef _WIN32
#include "Runtime/Platform/Windows/DarkMode.h"
#endif
@@ -28,8 +29,8 @@ EXPORTED InteropStatus InfiniFrameNativeTests_MacPooledHostCount(size_t* value)
}
#endif
EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
- const InfiniFrameInitParams* params,
- InfiniFrameInitParams** new_params
+ const InfiniFrameWindowInitParams* params,
+ InfiniFrameWindowInitParams** new_params
) {
if (new_params != nullptr) {
*new_params = nullptr;
@@ -42,7 +43,7 @@ EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
return;
}
- *new_params = new InfiniFrameInitParams();
+ *new_params = new InfiniFrameWindowInitParams();
// Content strings
(*new_params)->StartString = DuplicateString(params->StartString);
@@ -54,9 +55,6 @@ EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
(*new_params)->TemporaryFilesPath = DuplicateString(params->TemporaryFilesPath);
(*new_params)->UserAgent = DuplicateString(params->UserAgent);
(*new_params)->BrowserControlInitParameters = DuplicateString(params->BrowserControlInitParameters);
- (*new_params)->WebView2RuntimePath = DuplicateString(params->WebView2RuntimePath);
- (*new_params)->NotificationRegistrationId = DuplicateString(params->NotificationRegistrationId);
- (*new_params)->WindowsAppUserModelId = DuplicateString(params->WindowsAppUserModelId);
(*new_params)->DefaultNotificationIcon = DuplicateString(params->DefaultNotificationIcon);
// Runtime configuration
@@ -64,6 +62,7 @@ EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
// Parent window
(*new_params)->ParentInstance = params->ParentInstance;
+ (*new_params)->ApplicationInstance = params->ApplicationInstance;
// Event callbacks
(*new_params)->ClosingHandler = params->ClosingHandler;
@@ -79,7 +78,7 @@ EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
(*new_params)->DebugEventHandler = params->DebugEventHandler;
// Custom scheme support
- for (size_t i = 0; i < InfiniFrameInitParams::MaxCustomSchemeNames; ++i) {
+ for (size_t i = 0; i < InfiniFrameWindowInitParams::MaxCustomSchemeNames; ++i) {
(*new_params)->CustomSchemeNames[i] = params->CustomSchemeNames[i] != nullptr
? DuplicateString(params->CustomSchemeNames[i])
: nullptr;
@@ -143,7 +142,7 @@ EXPORTED InteropStatus InfiniFrameNativeTests_NativeParametersReturnAsIs(
});
}
-EXPORTED InteropStatus InfiniFrameNativeTests_FreeInitParams(InfiniFrameInitParams* params) {
+EXPORTED InteropStatus InfiniFrameNativeTests_FreeInitParams(InfiniFrameWindowInitParams* params) {
return RunExportStatus(
[&] {
if (!EnsureNotNull(params, "params")) {
@@ -158,11 +157,8 @@ EXPORTED InteropStatus InfiniFrameNativeTests_FreeInitParams(InfiniFrameInitPara
delete[] params->TemporaryFilesPath;
delete[] params->UserAgent;
delete[] params->BrowserControlInitParameters;
- delete[] params->WebView2RuntimePath;
- delete[] params->NotificationRegistrationId;
- delete[] params->WindowsAppUserModelId;
delete[] params->DefaultNotificationIcon;
- for (size_t i = 0; i < InfiniFrameInitParams::MaxCustomSchemeNames; ++i) {
+ for (size_t i = 0; i < InfiniFrameWindowInitParams::MaxCustomSchemeNames; ++i) {
delete[] params->CustomSchemeNames[i];
}
delete[] params->MenuBarJson;
@@ -171,6 +167,41 @@ EXPORTED InteropStatus InfiniFrameNativeTests_FreeInitParams(InfiniFrameInitPara
});
}
+EXPORTED InteropStatus InfiniFrameNativeTests_NativeApplicationParametersReturnAsIs(
+ const InfiniFrameApplicationInitParams* params,
+ InfiniFrameApplicationInitParams** new_params
+ ) {
+ if (new_params != nullptr) *new_params = nullptr;
+
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(params, "params") ||
+ !EnsureNotNull(new_params, "new_params", ::InteropStatus::OutParameterSetToInvalidNull)) {
+ return;
+ }
+
+ *new_params = new InfiniFrameApplicationInitParams {
+ DuplicateString(params->WebView2RuntimePath),
+ DuplicateString(params->NotificationRegistrationId),
+ DuplicateString(params->AppUserModelId),
+ DuplicateString(params->DefaultNotificationIcon),
+ params->StructSize
+ };
+ });
+}
+
+EXPORTED InteropStatus InfiniFrameNativeTests_FreeApplicationParameters(InfiniFrameApplicationInitParams* params) {
+ return RunExportStatus(
+ [&] {
+ if (!EnsureNotNull(params, "params")) return;
+ delete[] params->WebView2RuntimePath;
+ delete[] params->NotificationRegistrationId;
+ delete[] params->AppUserModelId;
+ delete[] params->DefaultNotificationIcon;
+ delete params;
+ });
+}
+
EXPORTED InteropStatus InfiniFrameNativeTests_ConsumeCustomSchemeResponse(
void* callbackPointer,
uint64_t* contentLength,
@@ -228,4 +259,4 @@ EXPORTED InteropStatus InfiniFrameNativeTests_IsColorSchemeChange(const LPARAM l
#endif
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/Utilities.h b/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/Utilities.h
deleted file mode 100644
index 5e5c92069..000000000
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/Utilities.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-// ---------------------------------------------------------------------------------------------------------------------
-// Imports
-// ---------------------------------------------------------------------------------------------------------------------
-#include "ExportErrorState.h"
-#include "ExportExecution.h"
-#include "ExportStringHelpers.h"
-#include "ExportValidation.h"
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Code
-// ---------------------------------------------------------------------------------------------------------------------
-using infiniframe::exports::EnsureNotNull;
-using infiniframe::exports::EnsureOutNotNull;
-using infiniframe::exports::GetLastErrorMessageCopy;
-using infiniframe::exports::ResetOut;
-using infiniframe::exports::ResetOut2;
-using infiniframe::exports::RunExportStatus;
-using infiniframe::exports::RunReturnExport;
-using infiniframe::exports::RunWindowExportStatus;
-using infiniframe::exports::RunWindowReturnExport;
-using infiniframe::exports::NullToEmpty;
-using infiniframe::exports::DuplicateString;
\ No newline at end of file
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Dependencies/VENDORING.md b/src/InfiniFrame.NativeBridge/Native/src/Dependencies/VENDORING.md
index a1fddb6bb..9021e88e8 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Dependencies/VENDORING.md
+++ b/src/InfiniFrame.NativeBridge/Native/src/Dependencies/VENDORING.md
@@ -1,6 +1,6 @@
# Vendored Native Dependencies
-`src/InfiniFrame.Native/Dependencies` contains vendored native dependency artifacts used by `InfiniFrame.Native`.
+`src/InfiniFrame.NativeBridge/Native/src/Dependencies` contains vendored native dependency artifacts used by `InfiniFrame.Native`.
## Libraries
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.cpp b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.cpp
new file mode 100644
index 000000000..c838ea0d0
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.cpp
@@ -0,0 +1,243 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include "Runtime/Internal/Application/InfiniFrameApplication.h"
+#include
+#include
+#include
+#include
+#include
+#ifdef _WIN32
+#include
+#include
+#include "Runtime/Platform/Windows/Window.Win32.Context.h"
+#include "Dependencies/wintoastlib/wintoastlib.h"
+#endif
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+struct InfiniFrameApplicationImpl {
+ mutable std::mutex mutex;
+ std::unordered_set windows;
+ std::unordered_set liveWindows;
+ bool registered = false;
+ bool shutdownRequested = false;
+ std::string webView2RuntimePath;
+ std::string notificationRegistrationId;
+ std::string appUserModelId;
+ std::string defaultNotificationIcon;
+ bool notificationsRegistered = false;
+#ifdef _WIN32
+ unsigned long runThreadId = 0;
+ bool running = false;
+#endif
+};
+
+namespace {
+ std::atomic applicationInstance = nullptr;
+}
+
+#ifdef _WIN32
+namespace {
+ std::wstring ToWindowsString(const char* value) {
+ if (value == nullptr || value[0] == '\0') return {};
+ const int length = MultiByteToWideChar(CP_UTF8, 0, value, -1, nullptr, 0);
+ if (length <= 1) return {};
+ std::wstring result(static_cast(length), L'\0');
+ MultiByteToWideChar(CP_UTF8, 0, value, -1, result.data(), length);
+ result.resize(static_cast(length - 1));
+ return result;
+ }
+}
+#endif
+
+InfiniFrameApplication::InfiniFrameApplication()
+ : _impl(std::make_unique()) {
+ InfiniFrameApplication* expected = nullptr;
+ if (!applicationInstance.compare_exchange_strong(expected, this, std::memory_order_acq_rel))
+ throw std::runtime_error("Only one InfiniFrameApplication may exist per process.");
+}
+
+InfiniFrameApplication::~InfiniFrameApplication() {
+ std::lock_guard lock(_impl->mutex);
+ _impl->windows.clear();
+ _impl->liveWindows.clear();
+ InfiniFrameApplication* expected = this;
+ applicationInstance.compare_exchange_strong(expected, nullptr, std::memory_order_acq_rel);
+}
+
+InfiniFrameApplication* InfiniFrameApplication::GetInstance() noexcept {
+ return applicationInstance.load(std::memory_order_acquire);
+}
+
+void InfiniFrameApplication::Register() {
+ std::lock_guard lock(_impl->mutex);
+#ifdef _WIN32
+ InfiniFrameWindow::Register(GetModuleHandle(nullptr));
+ if (_impl->registered) return;
+ if (!_impl->appUserModelId.empty()) {
+ const std::wstring appUserModelId = ToWindowsString(_impl->appUserModelId.c_str());
+ const HRESULT result = SetCurrentProcessExplicitAppUserModelID(appUserModelId.c_str());
+ if (FAILED(result))
+ throw std::runtime_error("Could not set the application Windows AppUserModelID.");
+ }
+
+#endif
+ _impl->registered = true;
+}
+
+void InfiniFrameApplication::Configure(const InfiniFrameApplicationInitParams& parameters) {
+ std::lock_guard lock(_impl->mutex);
+ _impl->webView2RuntimePath = parameters.WebView2RuntimePath == nullptr ? "" : parameters.WebView2RuntimePath;
+ _impl->notificationRegistrationId = parameters.NotificationRegistrationId == nullptr ? "" : parameters.NotificationRegistrationId;
+ _impl->appUserModelId = parameters.AppUserModelId == nullptr ? "" : parameters.AppUserModelId;
+ _impl->defaultNotificationIcon = parameters.DefaultNotificationIcon == nullptr ? "" : parameters.DefaultNotificationIcon;
+}
+
+#ifdef _WIN32
+void InfiniFrameApplication::EnsureNotificationsInitialized(const char* appName) {
+ std::lock_guard lock(_impl->mutex);
+ if (_impl->notificationsRegistered) return;
+ const std::string identity = !_impl->notificationRegistrationId.empty()
+ ? _impl->notificationRegistrationId
+ : !_impl->appUserModelId.empty() ? _impl->appUserModelId
+ : appName != nullptr && appName[0] != '\0' ? appName
+ : "InfiniFrame";
+
+ const char* effectiveAppName = appName != nullptr && appName[0] != '\0' ? appName : identity.c_str();
+ const std::wstring windowsAppName = ToWindowsString(effectiveAppName);
+ const std::wstring windowsIdentity = ToWindowsString(identity.c_str());
+ WinToastLib::setDebugOutputEnabled(false);
+ WinToastLib::WinToast::instance()->setAppName(windowsAppName);
+ WinToastLib::WinToast::instance()->setAppUserModelId(windowsIdentity);
+ if (!WinToastLib::WinToast::instance()->initialize())
+ throw std::runtime_error("Could not initialize application notifications.");
+ _impl->notificationsRegistered = true;
+}
+#endif
+
+void InfiniFrameApplication::Run() noexcept {
+#ifdef _WIN32
+ MSG message = {};
+ PeekMessage(&message, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
+
+ {
+ std::lock_guard lock(_impl->mutex);
+ _impl->runThreadId = GetCurrentThreadId();
+ _impl->running = true;
+ // Shutdown is a drain request, not permission to abandon HWNDs. A close can
+ // be deferred while WebView2 is creating its controller, so the message pump
+ // must continue until every tracked window has reached WM_DESTROY.
+ if (_impl->windows.empty()) {
+ _impl->running = false;
+ return;
+ }
+ }
+
+ while (true) {
+ {
+ std::lock_guard lock(_impl->mutex);
+ if (_impl->shutdownRequested && _impl->windows.empty()) break;
+ }
+
+ MsgWaitForMultipleObjectsEx(0, nullptr, 50, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
+ while (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
+ if (message.message == WM_QUIT) {
+ std::vector windows;
+ {
+ std::lock_guard lock(_impl->mutex);
+ _impl->shutdownRequested = true;
+ windows.assign(_impl->windows.begin(), _impl->windows.end());
+ }
+ // WM_QUIT does not dispatch to HWNDs. Post WM_CLOSE so normal
+ // teardown drains every tracked window before returning.
+ for (InfiniFrameWindow* window : windows)
+ window->Close();
+ break;
+ }
+ TranslateMessage(&message);
+ DispatchMessage(&message);
+ }
+ }
+
+ std::lock_guard lock(_impl->mutex);
+ _impl->running = false;
+ _impl->runThreadId = 0;
+#else
+ // Other platforms retain their existing loop until their application
+ // lifecycle integrations are implemented.
+#endif
+}
+
+void InfiniFrameApplication::Shutdown() noexcept {
+ std::lock_guard lock(_impl->mutex);
+ _impl->shutdownRequested = true;
+#ifdef _WIN32
+ if (_impl->runThreadId != 0) {
+ PostThreadMessage(_impl->runThreadId, WM_QUIT, 0, 0);
+ }
+#endif
+}
+
+void InfiniFrameApplication::TrackWindow(InfiniFrameWindow* window) {
+ if (window == nullptr) return;
+ std::lock_guard lock(_impl->mutex);
+ _impl->windows.insert(window);
+ _impl->liveWindows.insert(window);
+}
+
+void InfiniFrameApplication::UntrackWindow(InfiniFrameWindow* window) noexcept {
+ if (window == nullptr) return;
+ std::lock_guard lock(_impl->mutex);
+ _impl->windows.erase(window);
+ _impl->liveWindows.erase(window);
+}
+
+void InfiniFrameApplication::NotifyWindowClosed(InfiniFrameWindow* window) noexcept {
+ if (window == nullptr) return;
+
+ std::lock_guard lock(_impl->mutex);
+ _impl->windows.erase(window);
+#ifdef _WIN32
+ if (_impl->running && _impl->windows.empty())
+ PostThreadMessage(_impl->runThreadId, WM_QUIT, 0, 0);
+#endif
+}
+
+std::size_t InfiniFrameApplication::GetWindowCount() const noexcept {
+ std::lock_guard lock(_impl->mutex);
+ return _impl->liveWindows.size();
+}
+
+const char* InfiniFrameApplication::GetWebView2RuntimePath() const noexcept {
+ thread_local std::string value;
+ std::lock_guard lock(_impl->mutex);
+ value = _impl->webView2RuntimePath;
+ return value.c_str();
+}
+
+const char* InfiniFrameApplication::GetNotificationRegistrationId() const noexcept {
+ thread_local std::string value;
+ std::lock_guard lock(_impl->mutex);
+ value = _impl->notificationRegistrationId;
+ return value.c_str();
+}
+
+const char* InfiniFrameApplication::GetAppUserModelId() const noexcept {
+ thread_local std::string value;
+ std::lock_guard lock(_impl->mutex);
+ value = _impl->appUserModelId;
+ return value.c_str();
+}
+
+const char* InfiniFrameApplication::GetDefaultNotificationIcon() const noexcept {
+ thread_local std::string value;
+ std::lock_guard lock(_impl->mutex);
+ value = _impl->defaultNotificationIcon;
+ return value.c_str();
+}
+
+bool InfiniFrameApplication::HasNotificationRegistration() const noexcept {
+ return _impl->notificationsRegistered;
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.h
new file mode 100644
index 000000000..0f9e48c24
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Application/InfiniFrameApplication.h
@@ -0,0 +1,47 @@
+#pragma once
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+#include
+
+#include "Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h"
+
+class InfiniFrameWindow;
+struct InfiniFrameApplicationImpl;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+/**
+ * @brief Process-scoped owner and registry for native InfiniFrame windows.
+ *
+ * The registry is independent of the platform message loop so the existing
+ * window ABI can remain compatible while application-loop ownership is added.
+ */
+class InfiniFrameApplication {
+ public:
+ InfiniFrameApplication();
+ ~InfiniFrameApplication();
+
+ InfiniFrameApplication(const InfiniFrameApplication&) = delete;
+ InfiniFrameApplication& operator=(const InfiniFrameApplication&) = delete;
+
+ [[nodiscard]] static InfiniFrameApplication* GetInstance() noexcept;
+ void Register();
+ void Configure(const InfiniFrameApplicationInitParams& parameters);
+ void Run() noexcept;
+ void Shutdown() noexcept;
+ void TrackWindow(InfiniFrameWindow* window);
+ void UntrackWindow(InfiniFrameWindow* window) noexcept;
+ void NotifyWindowClosed(InfiniFrameWindow* window) noexcept;
+ [[nodiscard]] std::size_t GetWindowCount() const noexcept;
+ [[nodiscard]] const char* GetWebView2RuntimePath() const noexcept;
+ [[nodiscard]] const char* GetNotificationRegistrationId() const noexcept;
+ [[nodiscard]] const char* GetAppUserModelId() const noexcept;
+ [[nodiscard]] const char* GetDefaultNotificationIcon() const noexcept;
+ [[nodiscard]] bool HasNotificationRegistration() const noexcept;
+ void EnsureNotificationsInitialized(const char* appName);
+
+ private:
+ std::unique_ptr _impl;
+};
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.cpp b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.cpp
similarity index 99%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.cpp
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.cpp
index 3fc39a1f6..729f2d201 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.cpp
@@ -8,4 +8,4 @@
namespace infiniframe::exports {
thread_local std::string g_lastErrorMessage;
thread_local InteropStatus g_lastStatus = InteropStatus::Success;
-}
\ No newline at end of file
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.h
similarity index 95%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.h
index cf28afc77..fd1e6568e 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportErrorState.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportErrorState.h
@@ -16,9 +16,8 @@
#include
#include
-#include "Runtime/Shared/Window/InfiniFrame.h"
-#include "Runtime/Shared/Utilities/InteropStatus.h"
-#include "Api/Utilities/ExportStringHelpers.h"
+#include "Runtime/Internal/Interop/Types/InteropStatus.h"
+#include "Runtime/Internal/Interop/Exports/ExportStringHelpers.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportExecution.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportExecution.h
similarity index 97%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportExecution.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportExecution.h
index 48a985951..c3cf4a8dc 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportExecution.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportExecution.h
@@ -5,8 +5,8 @@
#include
#include
-#include "Runtime/Shared/Window/InfiniFrame.h"
-#include "Runtime/Shared/Utilities/InteropStatus.h"
+#include "Runtime/Internal/Interop/Types/InfiniFrameWindow.h"
+#include "Runtime/Internal/Interop/Types/InteropStatus.h"
#include "ExportErrorState.h"
#include "ExportValidation.h"
// ---------------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportStringHelpers.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportStringHelpers.h
similarity index 97%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportStringHelpers.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportStringHelpers.h
index bb6d7fd25..8e1a5080b 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportStringHelpers.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportStringHelpers.h
@@ -13,7 +13,7 @@
#include
#include
-#include "Runtime/Shared/Window/InfiniFrame.h"
+#include "Runtime/Internal/Utilities/StringCopy.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportValidation.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportValidation.h
similarity index 96%
rename from src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportValidation.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportValidation.h
index fcc949f38..29c3ccce1 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Api/Utilities/ExportValidation.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Exports/ExportValidation.h
@@ -16,8 +16,7 @@
#include
#include
-#include "Runtime/Shared/Window/InfiniFrame.h"
-#include "Runtime/Shared/Utilities/InteropStatus.h"
+#include "Runtime/Internal/Interop/Types/InteropStatus.h"
#include "ExportErrorState.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/Basic.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/Basic.h
similarity index 100%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/Basic.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/Basic.h
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/Callbacks.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/Callbacks.h
similarity index 79%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/Callbacks.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/Callbacks.h
index 1fd608230..38e0edf3e 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/Callbacks.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/Callbacks.h
@@ -4,8 +4,9 @@
// ---------------------------------------------------------------------------------------------------------------------
#include
-#include "Basic.h"
-#include "Monitor.h"
+#include "Runtime/Internal/Interop/Types/Basic.h"
+#include "Runtime/Internal/Interop/Types/Monitor.h"
+#include "Runtime/Internal/Interop/Types/CustomSchemeResponse.h"
// ---------------------------------------------------------------------------------------------------------------------
// Code
// ---------------------------------------------------------------------------------------------------------------------
@@ -63,40 +64,6 @@ using DebugEventCallback = void (*)(
const char* platformPayload
);
-/** Version 1 custom-scheme response body kinds. Kind 2 is reserved for a future pull-based stream ABI. */
-enum class CustomSchemeBodyKind : uint32_t {
- Buffered = 1,
- Stream = 2
-};
-
-using ReleaseCustomSchemeResponseCallback = void (*)(void* ownerContext);
-
-/**
- * @brief Versioned custom-scheme response descriptor shared with .NET.
- *
- * The native caller owns this descriptor. The producer owns Body, ContentTypeUtf8, and OwnerContext until native calls
- * Release(OwnerContext) exactly once. Native must not free any field directly. ReservedRead/ReservedSeek are ABI space
- * for a future streaming body kind and must be null for buffered responses.
- */
-struct CustomSchemeResponse {
- static constexpr uint32_t CurrentAbiVersion = 1;
- static constexpr uint64_t MaxBufferedBodyBytes = 256ULL * 1024ULL * 1024ULL;
-
- uint32_t StructSize;
- uint32_t AbiVersion;
- uint32_t StatusCode;
- uint32_t BodyKind;
- uint64_t ContentLength;
- const uint8_t* Body;
- const char* ContentTypeUtf8;
- void* OwnerContext;
- ReleaseCustomSchemeResponseCallback Release;
- void* ReservedRead;
- void* ReservedSeek;
-};
-
-static_assert(sizeof(uintptr_t) != 8 || sizeof(CustomSchemeResponse) == 72, "Unexpected 64-bit response ABI layout");
-
/**
* @brief Called when the WebView requests a custom-scheme resource.
* @param url Platform-native URL (UTF-8); borrowed for the duration of the call
@@ -167,4 +134,4 @@ using NavigationStartingCallback = int (*)(const char* url, int isUserInitiated,
* @param x Screen X coordinate of drop location
* @param y Screen Y coordinate of drop location
*/
-using FileDroppedCallback = void (*)(const char** paths, int count, int x, int y);
\ No newline at end of file
+using FileDroppedCallback = void (*)(const char** paths, int count, int x, int y);
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/CustomSchemeResponse.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/CustomSchemeResponse.h
new file mode 100644
index 000000000..5f40598cc
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/CustomSchemeResponse.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include
+
+/** Version 1 custom-scheme response body kinds. Kind 2 is reserved for streaming. */
+enum class CustomSchemeBodyKind : uint32_t {
+ Buffered = 1,
+ Stream = 2
+};
+
+using ReleaseCustomSchemeResponseCallback = void (*)(void* ownerContext);
+
+/** Versioned response descriptor shared with the managed ABI. */
+struct CustomSchemeResponse {
+ static constexpr uint32_t CurrentAbiVersion = 1;
+ static constexpr uint64_t MaxBufferedBodyBytes = 256ULL * 1024ULL * 1024ULL;
+
+ uint32_t StructSize;
+ uint32_t AbiVersion;
+ uint32_t StatusCode;
+ uint32_t BodyKind;
+ uint64_t ContentLength;
+ const uint8_t* Body;
+ const char* ContentTypeUtf8;
+ void* OwnerContext;
+ ReleaseCustomSchemeResponseCallback Release;
+ void* ReservedRead;
+ void* ReservedSeek;
+};
+
+static_assert(sizeof(uintptr_t) != 8 || sizeof(CustomSchemeResponse) == 72,
+ "Unexpected 64-bit response ABI layout");
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogButtons.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogButtons.h
similarity index 100%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogButtons.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogButtons.h
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogIcon.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogIcon.h
similarity index 100%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogIcon.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogIcon.h
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogResult.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogResult.h
similarity index 100%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Types/DialogResult.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/DialogResult.h
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h
new file mode 100644
index 000000000..5b4b39c36
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameApplicationInitParams.h
@@ -0,0 +1,24 @@
+#pragma once
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+/**
+ * @brief Process-wide initialization parameters for InfiniFrameApplication.
+ *
+ * Field order defines the ABI layout shared with the managed
+ * InfiniFrameNativeApplicationParameters type. New fields must be appended
+ * before StructSize and the managed/native layouts must be updated together.
+ */
+struct InfiniFrameApplicationInitParams {
+ const char* WebView2RuntimePath;
+ const char* NotificationRegistrationId;
+ const char* AppUserModelId;
+ const char* DefaultNotificationIcon;
+
+ // ABI version/size marker. This field must remain last.
+ int StructSize;
+};
diff --git a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Window/InfiniFrameWindow.h b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameWindow.h
similarity index 96%
rename from src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Window/InfiniFrameWindow.h
rename to src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameWindow.h
index 4c8da0105..6e8cc6c29 100644
--- a/src/InfiniFrame.NativeBridge/Native/src/Runtime/Shared/Window/InfiniFrameWindow.h
+++ b/src/InfiniFrame.NativeBridge/Native/src/Runtime/Internal/Interop/Types/InfiniFrameWindow.h
@@ -2,46 +2,53 @@
// ---------------------------------------------------------------------------------------------------------------------
// Imports
// ---------------------------------------------------------------------------------------------------------------------
-#ifdef _WIN32
-#include
-#include
-#include
-#endif
-
-#ifdef __APPLE__
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#endif
-
-#ifdef __linux__
-#include
-#include
-#endif
-
-#include