Skip to main content

Breaking Changes from Photino

InfiniFrame is a full, independent rework of Photino.NET, Photino.Native, and the rest of the Photino ecosystem. It's not a drop-in replacement, and some API decisions and general design choices are intentionally different. This document walks through what changed to help you migrate.

Table of Contents

Package and Namespace

AspectPhotinoInfiniFrame
NuGet packagePhotino.NETInfiniLore.InfiniFrame
C# namespacePhotino.NETInfiniFrame
Native DLLPhotino.NativeInfiniFrame.Native (internal)
C++ classPhotinoInfiniFrameWindow
C++ init paramsPhotinoInitParamsInfiniFrameInitParams
Exported function prefixPhotino_InfiniFrame_
Default window title"Photino""InfiniFrame"
Default user agent"Photino WebView""InfiniFrame WebView"
Default temp path%LocalAppData%\Photino (Windows only)%TEMP%\infiniframe (all platforms)

Every type that was prefixed Photino is now prefixed InfiniFrame. If you're calling the native DLL directly via P/Invoke, all exported symbol names need to change from Photino_* to InfiniFrame_*.

Entry Point and Builder API

Photino: direct construction

In Photino, you construct PhotinoWindow directly and configure it by calling methods on the object:

var window = new PhotinoWindow()
.SetTitle("My App")
.SetDevToolsEnabled(true)
.Load(new Uri("https://example.com"));
window.WaitForClose();

InfiniFrame: explicit builder

InfiniFrame uses a dedicated builder. Configuration must be set before calling Build():

IInfiniFrameWindow window = InfiniFrameWindowBuilder.Create()
.SetTitle("My App")
.SetDevToolsEnabled(true)
.SetStartUrl("https://example.com")
.Build();
window.WaitForClose();

The builder optionally accepts an IServiceProvider for DI integration. The returned type is IInfiniFrameWindow, not a concrete class.

Configuration from appsettings.json

InfiniFrame can pull window configuration from IConfiguration under an "InfiniFrame" key. This wasn't available in Photino:

{
"InfiniFrame": {
"Title": "My App",
"Width": 1280,
"Height": 720
}
}

Type and class changes

PhotinoInfiniFrameNotes
PhotinoWindow (monolithic)IInfiniFrameWindow (interface) + InfiniFrameWindow (concrete)Users interact through the interface
No builderInfiniFrameWindowBuilderSeparate builder class
No configuration typeInfiniFrameWindowConfigurationSeparates build-time config from runtime
PhotinoWindow(PhotinoWindow parent) constructorbuilder.SetParent(parentWindow)Parent is now passed through the builder

Runtime Window API

Method renames and removals

PhotinoInfiniFrameNotes
PhotinoWindow.Load(Uri) / Load(string)IInfiniFrameWindow.Load(Uri) / Load(string)Available at runtime; initial URL can also be set via SetStartUrl() in the builder
PhotinoWindow.LoadRawString(string)IInfiniFrameWindow.LoadRawString(string)Available at runtime; initial HTML can also be set via SetStartString() in the builder
PhotinoWindow.Center()IInfiniFrameWindow.Center() / CenterOnCurrentMonitor() / CenterOnMonitor(int)Available at runtime
PhotinoWindow.MoveTo(Point, bool) / Offset(Point)window.SetLocation(x, y) / window.Offset(x, y)
PhotinoWindow.SetMinHeight(int) / SetMinWidth(int)Removed; use SetMinSize(width, height)Consolidated
PhotinoWindow.SetMaxHeight(int) / SetMaxWidth(int)Removed; use SetMaxSize(width, height)Consolidated
PhotinoWindow.SetLogVerbosity(int)Removed; see Logging
PhotinoWindow.Win32SetWebView2Path(string)Internal; not on public interface
PhotinoWindow.MacOsVersion (static)Removed
PhotinoWindow.IsWindowsPlatform / IsMacOsPlatform / IsLinuxPlatform (static)Removed from public interface
PhotinoWindow.WaitForClose()IInfiniFrameWindow.WaitForClose() and WaitForCloseAsync()Async variant added
Monitor structInfiniMonitor recordType renamed. Collection changed from IReadOnlyList<Monitor> to ImmutableArray<InfiniMonitor>
PhotinoDialogButtons / PhotinoDialogResult / PhotinoDialogIconInfiniFrameDialogButtons / InfiniFrameDialogResult / InfiniFrameDialogIconRenamed enums
ShowSaveFile(title, defaultPath, filters, count)ShowSaveFile(title, defaultPath, filters, count, defaultFileName)Added defaultFileName parameter

New APIs not in Photino

APIDescription
IInfiniFrameWindow.FocusedQuery or set keyboard focus state
IInfiniFrameWindow.WaitForCloseAsync()Async wait for window close
IInfiniFrameWindow.ManagedThreadIdThread ID of the window's message loop
IInfiniFrameWindow.InstanceHandle / NativeTypeLow-level native access
IInfiniFrameWindow.CachedPreFullScreenBounds / CachedPreMaximizedBoundsSaved geometry for restore
RegisterCustomSchemeHandler()Returns IInfiniFrameWindow (fluent); in Photino it returned void
ZoomEnabledSeparate bool controlling whether the user can zoom, distinct from the Zoom level

Event System

Photino uses standard .NET EventHandler<T> delegates. Assigning a new handler replaces the previous one:

// Photino: last assignment wins
window.RegisterWindowClosingHandler((sender, args) => { ... });

InfiniFrame uses InfiniFrameOrderedEvent<T>, an ordered multi-subscriber system with deterministic execution order. Handlers are added via .Add() and run in the order they were registered:

// InfiniFrame: all handlers run in order
window.Events.WindowClosing.Add((window, args) => { ... });
window.Events.WindowClosing.Add((window, args) => { ... }); // also runs

Closing event split

Photino has a single RegisterWindowClosingHandler. InfiniFrame splits this into two:

EventTypeDescription
WindowClosingRequestedInfiniFrameOrderedClosingEventFired when close is requested; returning true from any handler cancels it
WindowClosingInfiniFrameOrderedEventFired when the window is definitively closing and cannot be cancelled

Event handler DI injection

When an IServiceProvider is passed to Build(), InfiniFrame event handlers support DI-resolved parameters. This isn't available in Photino:

window.Events.WindowClosing.Add((MyService svc, IInfiniFrameWindow w) => { ... });

Web Messaging and Message Routing

Photino: single raw handler

window.RegisterWebMessageReceivedHandler((sender, message) => {
// message is the full raw string from JS
});

One handler, full message string, no routing.

InfiniFrame: typed message routing

InfiniFrame uses a versioned JSON envelope protocol. Messages sent from JavaScript are dispatched to the handler registered for their id:

window.MessageHandlers.RegisterMessageHandler("myEvent", (window, payload) => {
// payload is "some data"
});

The JavaScript side must use the envelope format:

window.infiniframe.host.postData({ id: "myEvent", command: "Post", data: "some data", version: 2 });

RegisterWebMessageReceivedHandler is still available for raw message handling, but the legacy messageId;payload string format is gone. All messages must use the JSON envelope.

Web Security, CORS, and Trusted Origins

This is probably the most impactful behavioral change if you're coming from a typical Photino setup.

In Photino, projects often just disabled web security to get around CORS and module-loading problems:

// Common Photino workaround
windowBuilder.SetWebSecurityEnabled(false);

In InfiniFrame, browser web security and URI origin trust are separate concerns. SetWebSecurityEnabled(...) still controls browser engine security toggles, but origin trust is managed through InfiniFrameUriSecurityPolicy via builder APIs like AddTrustedOrigin, SetTrustedOrigins, and SetTrustAllOrigins.

For BlazorWebView, InfiniFrame serves content from app://localhost/ and validates requests and messages against trusted origins. On Windows this relies on WebView2 custom scheme support (ICoreWebView2EnvironmentOptions4). Linux and macOS use WebKit and aren't affected by WebView2 runtime availability.

The preferred migration pattern is to keep web security on and explicitly trust only the origins you need:

var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
wb.AddTrustedOrigin("https://xyz");
wb.AddTrustedOrigin("https://cdn.jsdelivr.net");
wb.AddTrustedOrigin("https://unpkg.com");
});

If you need broad compatibility during migration, you can opt in to trusting all origins:

var app = InfiniFrameBlazorAppBuilder.CreateDefault(windowBuilder: wb => {
wb.SetTrustAllOrigins(true);
});

SetTrustAllOrigins(true) is intentionally high-risk and should be treated as a temporary dev-time switch, not a production default.

New API surface compared to Photino

PurposeInfiniFrame API
Add one trusted originbuilder.AddTrustedOrigin("https://xyz")
Replace trusted origin listbuilder.SetTrustedOrigins("https://a", "https://b")
Trust all origins (explicit opt-in)builder.SetTrustAllOrigins(true)
Browser engine security togglebuilder.SetWebSecurityEnabled(bool)

Logging

Photino: integer verbosity

window.SetLogVerbosity(2); // 0 = silent, higher = more verbose

Logs went to Console.Out. There was also a known bug (#257) where setting verbosity to 0 would still emit a log message.

InfiniFrame: ILogger

The integer verbosity system is gone entirely. InfiniFrame integrates with Microsoft.Extensions.Logging:

var window = InfiniFrameWindowBuilder.Create()
.Build(serviceProvider); // ILogger<IInfiniFrameWindow> resolved from DI

Log output respects your configured logging provider and level filters.

Native C++ Interface

This section is only relevant if you're extending InfiniFrame at the native level or calling the native DLL directly.

Pimpl architecture

Photino's Photino class exposes platform-specific fields (_hWnd, GtkWidget*, NSWindow*) directly in its class declaration, which means platform headers have to be included everywhere the class is used.

InfiniFrame uses the Pimpl idiom throughout: a struct Impl held by std::unique_ptr<Impl> is defined per-platform in the .cpp/.mm file. The InfiniFrameWindow.h header is entirely platform-agnostic.

Build system and dependencies

AspectPhotinoInfiniFrame
Build systemVisual Studio .vcxproj + MakefileCMake 4.0
C++ standardC++17C++23
JSON libraryjson.hpp (nlohmann, bundled)simdjson via CMake FetchContent
String conversionCustom ToWide/ToUTF8String methodssimdutf via CMake FetchContent
FormattingNonestd::format (C++23 standard)
Sanitizers (Debug)NoneASan / UBSan / LeakSan enabled

Platform file layout

PhotinoInfiniFrame
Photino.Windows.cpp (all Windows code)Platform/Windows/Window.cpp
Photino.Linux.cpp (all Linux code)Platform/Linux/Window.cpp
Photino.Mac.mm (all macOS code)Platform/Mac/Window.mm + separate delegate files
macOS delegates inline in .mmAppDelegate, UiDelegate, WindowDelegate, NavigationDelegate, UrlSchemeHandler, NSWindowBorderless in separate files

P/Invoke generation

Photino uses manual [DllImport] attributes. InfiniFrame uses source-generated [LibraryImport] (requires .NET 7+):

// Photino
[DllImport("Photino.Native", ...)]
static extern void Photino_SetTitle(IntPtr instance, string title);

// InfiniFrame
[LibraryImport("InfiniFrame.Native", ...)]
static partial void InfiniFrame_SetTitle(IntPtr instance, ...);

String ownership

Photino has no explicit API for freeing native-allocated strings, which causes memory leaks in long-running applications.

InfiniFrame exports explicit free functions that must be called on any string returned from the native layer:

InfiniFrame_FreeString(ptr);
InfiniFrame_FreeStringArray(ptr, count);

The managed wrapper calls these automatically. If you're calling native exports directly, you're responsible for invoking them yourself.

SaveFileDialog signature change

The native SaveFileDialog export gained a defaultFileName parameter:

// Photino
Photino_ShowSaveFile(title, defaultPath, filters, count)

// InfiniFrame
InfiniFrame_ShowSaveFile(title, defaultPath, filters, count, defaultFileName)

Known Photino Issues Addressed

Photino IssueDescriptionHow InfiniFrame addresses it
photino.native #173/174Custom scheme handlers completely broken on Windows (WebResourceRequested never fires)Rewritten registration path; scheme handlers tested end-to-end in examples
photino.native #165Memory leak in SendWebMessage on WindowsExplicit InfiniFrame_FreeString ownership model; Pimpl isolates per-window native allocations
photino.native #158No way to programmatically focus a windowInfiniFrame_SetFocused / InfiniFrame_GetFocused exported and exposed via IInfiniFrameWindow.Focused
photino.native #163UTF encoding bug in SetWebView2RuntimePath silently corrupts non-ASCII pathssimdutf used for all UTF-8/UTF-16 conversions on Windows
photino.native #141Stack overflow in WaitForExit on LinuxPer-window independent message loops; no shared global MessageLoopState lock
photino.NET #75RegisterWindowClosingHandler does not fire on LinuxClosing handler rewritten using the GTK delete-event signal correctly
photino.NET #257SetLogVerbosity(0) still logs a messageInteger verbosity removed entirely and replaced by ILogger<IInfiniFrameWindow>
photino.NET #232Custom scheme handlers break fetch/XHR (CORS interference)Scheme handler registration refactored; CORS headers handled correctly per platform
photino.native #175SetTopmost uses wrong Win32 style; null crash on LinuxFixed Win32 HWND_TOPMOST/HWND_NOTOPMOST usage; null guards added on Linux

Removed or Replaced Features

FeatureNotes
SetMinHeight / SetMinWidth / SetMaxHeight / SetMaxWidthConsolidated into SetMinSize(w, h) / SetMaxSize(w, h)
LogVerbosity integer systemReplaced by ILogger<IInfiniFrameWindow>
MacOsVersion static propertyRemoved
IsWindowsPlatform / IsMacOsPlatform / IsLinuxPlatform static propertiesInternal; not on public interface
UseOsDefaultLocation / UseOsDefaultSize runtime propertiesBuilder / config time only
BrowserControlInitParameters runtime propertyBuilder / config time only
nlohmann/json.hpp bundled JSON headerReplaced by simdjson
fmt formatting libraryReplaced by std::format