From 05f7396412cd528718bf7dce643d6de543ac0338 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Thu, 7 Aug 2025 10:20:18 +0800 Subject: [PATCH 1/8] feat(examples): add advanced window, browser profiles, error handling, and secure communication demos - introduce advanced_window example with window controls and info - add browser_profiles example for managing browser profiles and info - provide error_handling example demonstrating error reporting and recovery - add secure_communication example for encoding, binary transfer, and memory management - update win32GetHwnd for improved pointer handling on Windows --- examples/advanced_window/index.html | 269 +++++++++++++++++++++++ examples/advanced_window/main.zig | 149 +++++++++++++ examples/browser_profiles/index.html | 103 +++++++++ examples/browser_profiles/main.zig | 93 ++++++++ examples/browser_profiles/profile.html | 35 +++ examples/error_handling/index.html | 237 ++++++++++++++++++++ examples/error_handling/main.zig | 207 +++++++++++++++++ examples/secure_communication/index.html | 187 ++++++++++++++++ examples/secure_communication/main.zig | 117 ++++++++++ src/webui.zig | 4 +- 10 files changed, 1399 insertions(+), 2 deletions(-) create mode 100644 examples/advanced_window/index.html create mode 100644 examples/advanced_window/main.zig create mode 100644 examples/browser_profiles/index.html create mode 100644 examples/browser_profiles/main.zig create mode 100644 examples/browser_profiles/profile.html create mode 100644 examples/error_handling/index.html create mode 100644 examples/error_handling/main.zig create mode 100644 examples/secure_communication/index.html create mode 100644 examples/secure_communication/main.zig diff --git a/examples/advanced_window/index.html b/examples/advanced_window/index.html new file mode 100644 index 0000000..f3efb40 --- /dev/null +++ b/examples/advanced_window/index.html @@ -0,0 +1,269 @@ + + + + Advanced Window Controls + + + +

🪟 Advanced Window Controls

+ +
+
+

Window Position

+ +

+ + + +
+ +
+

Window Size

+ + + +

Min size: 640x480

+
+ +
+

Window Visibility

+ + +

Note: Hide may not work after window is shown

+
+ +
+

MIME Type Detection

+ + +
Enter a filename to get its MIME type
+
+ +
+

Port Information

+ +
Click to get port information
+
+ +
+

Process Information

+ +
Click to get process information
+
+ +
+

⚠️ Danger Zone

+ +

This will close and destroy the window!

+
+
+ + + + \ No newline at end of file diff --git a/examples/advanced_window/main.zig b/examples/advanced_window/main.zig new file mode 100644 index 0000000..4add66d --- /dev/null +++ b/examples/advanced_window/main.zig @@ -0,0 +1,149 @@ +const std = @import("std"); +const webui = @import("webui"); +const builtin = @import("builtin"); + +pub fn main() !void { + const window = webui.newWindow(); + defer window.destroy(); + + // Set window properties before showing + window.setSize(1024, 768); + window.setMinimumSize(640, 480); + window.setPosition(100, 100); + + // Enable high contrast support + window.setHighContrast(true); + + // Check if high contrast is enabled system-wide + const high_contrast = webui.isHighConstrast(); + std.debug.print("System high contrast: {}\n", .{high_contrast}); + + // Set window to be hidden initially (uncomment to test) + // window.setHide(true); + + // Set connection timeout (0 means wait forever) + webui.setTimeout(30); // 30 seconds timeout + + // Set window icon (base64 encoded favicon) + const icon_svg = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PGNpcmNsZSBjeD0iOCIgY3k9IjgiIHI9IjgiIGZpbGw9IiM0Mjg1RjQiLz48L3N2Zz4="; + window.setIcon(icon_svg, "image/svg+xml"); + + // Bind window control events + _ = try window.bind("center_window", centerWindow); + _ = try window.bind("toggle_hide", toggleHide); + _ = try window.bind("resize_window", resizeWindow); + _ = try window.bind("move_window", moveWindow); + _ = try window.bind("get_mime", getMimeType); + _ = try window.bind("get_port", getPortInfo); + _ = try window.bind("get_process", getProcessInfo); + _ = try window.bind("destroy_window", destroyWindow); + + // Show the window + try window.show("index.html"); + + // Wait for window to close + webui.wait(); + + // Clean up + webui.clean(); +} + +fn centerWindow(e: *webui.Event) void { + const window = e.getWindow(); + window.setCenter(); + e.returnString("Window centered"); +} + +fn toggleHide(e: *webui.Event) void { + const window = e.getWindow(); + const hide = e.getBool(); + window.setHide(hide); + e.returnString(if (hide) "Window hidden" else "Window shown"); +} + +fn resizeWindow(e: *webui.Event) void { + const window = e.getWindow(); + const width = e.getIntAt(0); + const height = e.getIntAt(1); + + if (width > 0 and height > 0) { + window.setSize(@intCast(width), @intCast(height)); + e.returnString("Window resized"); + } else { + e.returnString("Invalid dimensions"); + } +} + +fn moveWindow(e: *webui.Event) void { + const window = e.getWindow(); + const x = e.getIntAt(0); + const y = e.getIntAt(1); + + if (x >= 0 and y >= 0) { + window.setPosition(@intCast(x), @intCast(y)); + e.returnString("Window moved"); + } else { + e.returnString("Invalid position"); + } +} + +fn getMimeType(e: *webui.Event) void { + const filename = e.getString(); + const mime = webui.getMimeType(filename); + e.returnString(mime); +} + +fn getPortInfo(e: *webui.Event) void { + const window = e.getWindow(); + + var buffer: [256]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + const writer = fbs.writer(); + + const port = window.getPort() catch 0; + const free_port = webui.getFreePort(); + + writer.print("Current port: {}\nFree port available: {}", .{ port, free_port }) catch {}; + + const written = fbs.getWritten(); + var null_terminated: [257]u8 = undefined; + @memcpy(null_terminated[0..written.len], written); + null_terminated[written.len] = 0; + + e.returnString(null_terminated[0..written.len :0]); +} + +fn getProcessInfo(e: *webui.Event) void { + const window = e.getWindow(); + + var buffer: [512]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + const writer = fbs.writer(); + + const parent_pid = window.getParentProcessId() catch 0; + const child_pid = window.getChildProcessId() catch 0; + + writer.print("Parent PID: {}\nChild PID: {}\n", .{ parent_pid, child_pid }) catch {}; + + // On Windows, also get HWND + if (builtin.os.tag == .windows) { + if (window.win32GetHwnd()) |hwnd| { + writer.print("Window HWND: {}", .{@intFromPtr(hwnd)}) catch {}; + } else |_| { + // HWND not available + } + } + + const written = fbs.getWritten(); + var null_terminated: [513]u8 = undefined; + @memcpy(null_terminated[0..written.len], written); + null_terminated[written.len] = 0; + + e.returnString(null_terminated[0..written.len :0]); +} + +fn destroyWindow(e: *webui.Event) void { + const window = e.getWindow(); + e.returnString("Window will be destroyed"); + window.destroy(); +} diff --git a/examples/browser_profiles/index.html b/examples/browser_profiles/index.html new file mode 100644 index 0000000..1080e3d --- /dev/null +++ b/examples/browser_profiles/index.html @@ -0,0 +1,103 @@ + + + + Browser Profiles Example + + + +
+

Browser Profiles Management

+ +
+

Current Profile

+

This window is using the default browser profile.

+

Custom parameters: --disable-gpu --disable-dev-shm-usage

+
+ +
+

Profile Actions

+ + + + +
+ +
+
+ + + + \ No newline at end of file diff --git a/examples/browser_profiles/main.zig b/examples/browser_profiles/main.zig new file mode 100644 index 0000000..5ab1c6e --- /dev/null +++ b/examples/browser_profiles/main.zig @@ -0,0 +1,93 @@ +const std = @import("std"); +const webui = @import("webui"); + +pub fn main() !void { + // Initialize windows + const main_window = webui.newWindow(); + defer main_window.destroy(); + + const profile_window = webui.newWindow(); + defer profile_window.destroy(); + + // Set custom browser folder if needed (optional) + // webui.setBrowserFolder("C:\\Program Files\\Google\\Chrome\\Application"); + + // Check if browsers exist + std.debug.print("Chrome exists: {}\n", .{webui.browserExist(.Chrome)}); + std.debug.print("Firefox exists: {}\n", .{webui.browserExist(.Firefox)}); + std.debug.print("Edge exists: {}\n", .{webui.browserExist(.Edge)}); + + // Get the best browser available + const best_browser = main_window.getBestBrowser(); + std.debug.print("Best browser: {}\n", .{best_browser}); + + // Set up the main window with default profile + main_window.setProfile("", ""); + + // Set proxy for the main window (optional) + // main_window.setProxy("http://proxy.example.com:8080"); + + // Set custom browser parameters + main_window.setCustomParameters("--disable-gpu --disable-dev-shm-usage"); + + // Bind events + _ = try main_window.bind("switch_profile", switchProfile); + _ = try main_window.bind("delete_profile", deleteCurrentProfile); + _ = try main_window.bind("show_info", showBrowserInfo); + + // Show main window + try main_window.show("index.html"); + + // Set up profile window with custom profile + profile_window.setProfile("TestProfile", ""); + + // Show profile window with different browser + try profile_window.showBrowser("profile.html", .Firefox); + // Wait for windows to close + webui.wait(); + + // Clean up profiles at the end + main_window.deleteProfile(); + profile_window.deleteProfile(); + + // Optionally delete all profiles + // webui.deleteAllProfiles(); +} + +fn switchProfile(e: *webui.Event) void { + // Get profile name from JavaScript + const profile_name = e.getString(); + + std.debug.print("Switching to profile: {s}\n", .{profile_name}); + + // Note: In a real application, you would close and reopen the window + // with the new profile since profiles can't be changed after show() + e.returnString("Profile switch requested. Please restart the window."); +} +fn deleteCurrentProfile(e: *webui.Event) void { + std.debug.print("Deleting current profile...\n", .{}); + + // This will delete the profile folder when the window closes + e.getWindow().deleteProfile(); + + e.returnString("Profile will be deleted when window closes."); +} +fn showBrowserInfo(e: *webui.Event) void { + var info_buffer: [1024]u8 = undefined; + var fbs = std.io.fixedBufferStream(&info_buffer); + const writer = fbs.writer(); + + writer.print("Browser Detection Info:\n", .{}) catch {}; + writer.print("Chrome: {}\n", .{webui.browserExist(.Chrome)}) catch {}; + writer.print("Firefox: {}\n", .{webui.browserExist(.Firefox)}) catch {}; + writer.print("Edge: {}\n", .{webui.browserExist(.Edge)}) catch {}; + writer.print("Safari: {}\n", .{webui.browserExist(.Safari)}) catch {}; + writer.print("Chromium: {}\n", .{webui.browserExist(.Chromium)}) catch {}; + writer.print("Best Browser: {}\n", .{e.getWindow().getBestBrowser()}) catch {}; + const written = fbs.getWritten(); + var null_terminated: [1025]u8 = undefined; + @memcpy(null_terminated[0..written.len], written); + null_terminated[written.len] = 0; + + e.returnString(null_terminated[0..written.len :0]); +} diff --git a/examples/browser_profiles/profile.html b/examples/browser_profiles/profile.html new file mode 100644 index 0000000..f39e421 --- /dev/null +++ b/examples/browser_profiles/profile.html @@ -0,0 +1,35 @@ + + + + Test Profile Window + + + +
+

Test Profile Window

+

This window is using a custom profile: TestProfile

+

It was opened with Firefox browser preference.

+

Profile data is isolated from the main window.

+
+ + \ No newline at end of file diff --git a/examples/error_handling/index.html b/examples/error_handling/index.html new file mode 100644 index 0000000..f1f3088 --- /dev/null +++ b/examples/error_handling/index.html @@ -0,0 +1,237 @@ + + + + Error Handling Example + + + +
+

⚠️ Error Handling Demonstration

+ +
+ ℹ️ About This Example:
+ This example demonstrates proper error handling in WebUI applications. + Each test below intentionally triggers different types of errors to show + how to handle them gracefully. +
+ +
+

Show Window Error

+

Attempts to show a non-existent file to trigger an error.

+ +
Click to test...
+
+ +
+

Port Configuration Error

+

Attempts to set a restricted port (port 1) to trigger an error.

+ +
Click to test...
+
+ +
+

Script Execution Error

+

Attempts to execute invalid JavaScript code.

+ +
Click to test...
+
+ +
+

Encoding/Decoding Error

+

Tests encoding and attempts to decode invalid base64 data.

+ +
Click to test...
+
+ +
+

Window ID Error

+

Attempts to create a window with an invalid ID (0).

+ +
Click to test...
+
+ +
+

Get Last Error Information

+

Retrieves the last error information from WebUI.

+ +
Click to get last error...
+
+ +
+

Timeout Configuration

+

Shows current timeout configuration.

+ +
Click to check...
+
+ +
+ ⚠️ Note: Some errors are expected and demonstrate proper error handling. +
+
+ + + + \ No newline at end of file diff --git a/examples/error_handling/main.zig b/examples/error_handling/main.zig new file mode 100644 index 0000000..b7ee4d2 --- /dev/null +++ b/examples/error_handling/main.zig @@ -0,0 +1,207 @@ +const std = @import("std"); +const webui = @import("webui"); + +pub fn main() !void { + // Demonstrate error handling throughout the application + std.debug.print("Starting Error Handling Example\n", .{}); + + // Set a timeout for connection (10 seconds) + webui.setTimeout(10); + + const window = webui.newWindow(); + defer window.destroy(); + + // Bind error demonstration functions + _ = try window.bind("test_show_error", testShowError); + _ = try window.bind("test_port_error", testPortError); + _ = try window.bind("test_script_error", testScriptError); + _ = try window.bind("test_encode_error", testEncodeError); + _ = try window.bind("test_window_id_error", testWindowIdError); + _ = try window.bind("get_last_error", getLastErrorInfo); + _ = try window.bind("test_timeout", testTimeout); + + // Show the main window + window.show("index.html") catch |err| { + std.debug.print("Failed to show window: {}\n", .{err}); + const error_info = webui.getLastError(); + std.debug.print("WebUI Error #{}: {s}\n", .{ error_info.num, error_info.msg }); + return err; + }; + + webui.wait(); +} + +fn testShowError(e: *webui.Event) void { + const window = e.getWindow(); + + // Try to show with invalid content (simulate error) + window.show("nonexistent_file_that_does_not_exist.html") catch |err| { + const error_info = webui.getLastError(); + + var buffer: [512]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + const writer = fbs.writer(); + + writer.print("Show Error: {}\nWebUI Error #{}: {s}", .{ err, error_info.num, error_info.msg }) catch {}; + + const written = fbs.getWritten(); + var null_terminated: [513]u8 = undefined; + @memcpy(null_terminated[0..written.len], written); + null_terminated[written.len] = 0; + + e.returnString(null_terminated[0..written.len :0]); + return; + }; + + e.returnString("No error occurred"); +} + +fn testPortError(e: *webui.Event) void { + const window = e.getWindow(); + + // Try to set an invalid port (port 1 is usually restricted) + window.setPort(1) catch |err| { + const error_info = webui.getLastError(); + + var buffer: [512]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + const writer = fbs.writer(); + + writer.print("Port Error: {}\nWebUI Error #{}: {s}", .{ err, error_info.num, error_info.msg }) catch {}; + + const written = fbs.getWritten(); + var null_terminated: [513]u8 = undefined; + @memcpy(null_terminated[0..written.len], written); + null_terminated[written.len] = 0; + + e.returnString(null_terminated[0..written.len :0]); + return; + }; + + e.returnString("Port set successfully (unexpected)"); +} + +fn testScriptError(e: *webui.Event) void { + const window = e.getWindow(); + + // Try to execute invalid JavaScript + var result_buffer: [256]u8 = undefined; + window.script("this_is_invalid_javascript()", 1000, &result_buffer) catch |err| { + const error_info = webui.getLastError(); + + var buffer: [512]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + const writer = fbs.writer(); + + writer.print("Script Error: {}\nWebUI Error #{}: {s}", .{ err, error_info.num, error_info.msg }) catch {}; + + const written = fbs.getWritten(); + var null_terminated: [513]u8 = undefined; + @memcpy(null_terminated[0..written.len], written); + null_terminated[written.len] = 0; + + e.returnString(null_terminated[0..written.len :0]); + return; + }; + + e.returnString("Script executed (unexpected)"); +} + +fn testEncodeError(e: *webui.Event) void { + // Test encoding with proper error handling + const test_string = "Test encoding string"; + + const encoded = webui.encode(test_string) catch |err| { + var buffer: [256]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + const writer = fbs.writer(); + + writer.print("Encode Error: {}", .{err}) catch {}; + + const written = fbs.getWritten(); + var null_terminated: [257]u8 = undefined; + @memcpy(null_terminated[0..written.len], written); + null_terminated[written.len] = 0; + + e.returnString(null_terminated[0..written.len :0]); + return; + }; + defer webui.free(encoded); + + // Try to decode invalid base64 + const invalid_base64 = "This is not valid base64!!!"; + const decoded = webui.decode(invalid_base64) catch |err| { + var buffer: [256]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + const writer = fbs.writer(); + + writer.print("Decode Error: {} (Expected for invalid base64)", .{err}) catch {}; + + const written = fbs.getWritten(); + var null_terminated: [257]u8 = undefined; + @memcpy(null_terminated[0..written.len], written); + null_terminated[written.len] = 0; + + e.returnString(null_terminated[0..written.len :0]); + return; + }; + defer webui.free(decoded); + + e.returnString("No errors occurred"); +} + +fn testWindowIdError(e: *webui.Event) void { + // Try to create window with invalid ID + const invalid_window = webui.newWindowWithId(0) catch |err| { + var buffer: [256]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + const writer = fbs.writer(); + + writer.print("Window ID Error: {} (ID 0 is invalid)", .{err}) catch {}; + + const written = fbs.getWritten(); + var null_terminated: [257]u8 = undefined; + @memcpy(null_terminated[0..written.len], written); + null_terminated[written.len] = 0; + + e.returnString(null_terminated[0..written.len :0]); + return; + }; + + invalid_window.destroy(); + e.returnString("Window created (unexpected)"); +} + +fn getLastErrorInfo(e: *webui.Event) void { + const error_info = webui.getLastError(); + + var buffer: [512]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + const writer = fbs.writer(); + + writer.print("Last Error Information:\nError Number: {}\nError Message: {s}", .{ error_info.num, error_info.msg }) catch {}; + + const written = fbs.getWritten(); + var null_terminated: [513]u8 = undefined; + @memcpy(null_terminated[0..written.len], written); + null_terminated[written.len] = 0; + + e.returnString(null_terminated[0..written.len :0]); +} + +fn testTimeout(e: *webui.Event) void { + // Demonstrate timeout configuration + var buffer: [256]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buffer); + const writer = fbs.writer(); + + writer.print("Current timeout is set to 10 seconds.\nSet to 0 for infinite wait.", .{}) catch {}; + + const written = fbs.getWritten(); + var null_terminated: [257]u8 = undefined; + @memcpy(null_terminated[0..written.len], written); + null_terminated[written.len] = 0; + + e.returnString(null_terminated[0..written.len :0]); +} + diff --git a/examples/secure_communication/index.html b/examples/secure_communication/index.html new file mode 100644 index 0000000..6217626 --- /dev/null +++ b/examples/secure_communication/index.html @@ -0,0 +1,187 @@ + + + + Secure Communication Example + + + +
+

🔒 Secure Communication & Encoding

+ +
+

Base64 Encoding

+ + +
+
+ +
+

Base64 Decoding

+ + +
+
+ +
+

Binary Data Transfer

+

Send and receive raw binary data between backend and frontend.

+ +
Waiting for binary data...
+
+ +
+

Memory Management Test

+

Test WebUI's memory allocation and management functions.

+ +
+
+
+ + + + \ No newline at end of file diff --git a/examples/secure_communication/main.zig b/examples/secure_communication/main.zig new file mode 100644 index 0000000..57a4caa --- /dev/null +++ b/examples/secure_communication/main.zig @@ -0,0 +1,117 @@ +const std = @import("std"); +const webui = @import("webui"); + +pub fn main() !void { + const window = webui.newWindow(); + defer window.destroy(); + + // Set up TLS certificate (only works with webui-2-secure library) + // In production, use real certificates + // webui.setTlsCertificate("", "") catch { + // std.debug.print("TLS not supported in this build\n", .{}); + // }; + + // Bind events for secure communication + _ = try window.bind("encode_data", encodeData); + _ = try window.bind("decode_data", decodeData); + _ = try window.bind("send_binary", sendBinaryData); + _ = try window.bind("test_memory", testMemoryManagement); + + // Show the window + try window.show("index.html"); + + // Wait for the window to close + webui.wait(); +} + +fn encodeData(e: *webui.Event) void { + // Get the data to encode from JavaScript + const data = e.getString(); + + std.debug.print("Encoding data: {s}\n", .{data}); + + // Encode the data using WebUI's base64 encoding + const encoded = webui.encode(data) catch |err| { + std.debug.print("Encoding failed: {}\n", .{err}); + e.returnString("Encoding failed"); + return; + }; + defer webui.free(encoded); + + // Create null-terminated string for return + var result_buffer: [1024]u8 = undefined; + const len = @min(encoded.len, result_buffer.len - 1); + @memcpy(result_buffer[0..len], encoded[0..len]); + result_buffer[len] = 0; + + e.returnString(result_buffer[0..len :0]); +} + +fn decodeData(e: *webui.Event) void { + // Get the base64 data to decode + const encoded_data = e.getString(); + + std.debug.print("Decoding data: {s}\n", .{encoded_data}); + + // Decode the data + const decoded = webui.decode(encoded_data) catch |err| { + std.debug.print("Decoding failed: {}\n", .{err}); + e.returnString("Decoding failed"); + return; + }; + defer webui.free(decoded); + + // Create null-terminated string for return + var result_buffer: [1024]u8 = undefined; + const len = @min(decoded.len, result_buffer.len - 1); + @memcpy(result_buffer[0..len], decoded[0..len]); + result_buffer[len] = 0; + + e.returnString(result_buffer[0..len :0]); +} + +fn sendBinaryData(e: *webui.Event) void { + const window = e.getWindow(); + + // Create some binary data + var binary_data = [_]u8{ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21 }; + + // Send raw binary data to the UI + window.sendRaw("receiveBinaryData", &binary_data); + + // Also demonstrate sending to specific client + e.sendRawClient("receiveClientData", &binary_data); + + e.returnString("Binary data sent"); +} + +fn testMemoryManagement(e: *webui.Event) void { + // Demonstrate WebUI's memory management + + // Allocate memory using WebUI's allocator + const size: usize = 256; + const buffer = webui.malloc(size) catch |err| { + std.debug.print("Memory allocation failed: {}\n", .{err}); + e.returnString("Memory allocation failed"); + return; + }; + defer webui.free(buffer); + + // Fill buffer with test data + const test_data = "This is test data for memory management"; + const copy_len = @min(test_data.len, buffer.len); + webui.memcpy(buffer[0..copy_len], test_data[0..copy_len]); + + // Create another buffer and copy data + const buffer2 = webui.malloc(size) catch { + e.returnString("Second allocation failed"); + return; + }; + defer webui.free(buffer2); + + webui.memcpy(buffer2[0..copy_len], buffer[0..copy_len]); + + // Return the copied data + buffer2[copy_len] = 0; + e.returnString(buffer2[0..copy_len :0]); +} diff --git a/src/webui.zig b/src/webui.zig index 5cae7ba..081a18c 100644 --- a/src/webui.zig +++ b/src/webui.zig @@ -458,8 +458,8 @@ pub fn win32GetHwnd(self: webui) !windows.HWND { @compileError("Note: method win32GetHwnd only can call on MS windows!"); } const tmp_hwnd = c.webui_win32_get_hwnd(self.window_handle); - if (tmp_hwnd) { - return @ptrCast(tmp_hwnd); + if (tmp_hwnd) |hwnd| { + return @ptrCast(hwnd); } else { return WebUIError.HWNDError; } From 72d5c816d8680238be49a7d320f29b7004009d6a Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sat, 23 Aug 2025 22:26:36 +0800 Subject: [PATCH 2/8] fix(examples): improve ArrayList compatibility for Zig 0.16+ - add version checks to support managed and unmanaged ArrayList initialization - update user append logic to handle allocator parameter for Zig 0.16+ - ensure event handling example works across Zig 0.14, 0.15, and 0.16+ --- examples/event_handling/main.zig | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/examples/event_handling/main.zig b/examples/event_handling/main.zig index 784a8b7..5989084 100644 --- a/examples/event_handling/main.zig +++ b/examples/event_handling/main.zig @@ -2,6 +2,7 @@ //! This example demonstrates advanced event handling, context management, and multi-client support const std = @import("std"); const webui = @import("webui"); +const builtin = @import("builtin"); const html = @embedFile("index.html"); @@ -25,7 +26,14 @@ fn ensureContextsInitialized() void { global_user_contexts = std.AutoHashMap(usize, *UserContext).init(allocator); } if (online_users == null) { - online_users = std.ArrayList(OnlineUser).init(allocator); + // Version compatibility: Zig 0.14/0.15 use managed ArrayList, 0.16+ use unmanaged + if (comptime builtin.zig_version.minor >= 16) { + // Zig 0.16+ - ArrayList is unmanaged by default + online_users = std.ArrayList(OnlineUser){}; + } else { + // Zig 0.14/0.15 - ArrayList is managed + online_users = std.ArrayList(OnlineUser).init(allocator); + } } } @@ -214,9 +222,18 @@ fn userLogin(e: *webui.Event) void { // Add user to online list ensureContextsInitialized(); if (online_users) |*users| { - users.append(OnlineUser{ .client_id = e.client_id, .username = context.username }) catch { - std.debug.print("Failed to add user to online list\n", .{}); - }; + // Version compatibility for append method + if (comptime builtin.zig_version.minor >= 16) { + // Zig 0.16+ - unmanaged ArrayList needs allocator parameter + users.append(allocator, OnlineUser{ .client_id = e.client_id, .username = context.username }) catch { + std.debug.print("Failed to add user to online list\n", .{}); + }; + } else { + // Zig 0.14/0.15 - managed ArrayList doesn't need allocator parameter + users.append(OnlineUser{ .client_id = e.client_id, .username = context.username }) catch { + std.debug.print("Failed to add user to online list\n", .{}); + }; + } } // Broadcast user list update to all clients From 28ee66cdbaa4112c2d7efe799e65ea94c94fe12a Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sat, 23 Aug 2025 22:53:39 +0800 Subject: [PATCH 3/8] chore(ci): add Zig 0.15.1 to workflow matrix - update CI configuration to test against Zig 0.15.1 - ensure compatibility across multiple Zig versions --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a1e3da..2c9e768 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - version: [0.14.0, ""] + version: [0.14.0, 0.15.1, ""] fail-fast: false runs-on: ${{ matrix.os }} steps: From 800d5750a57257582b54f3712e4e155757f911d3 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 24 Aug 2025 11:12:32 +0800 Subject: [PATCH 4/8] fix(event_handling): improve Zig version compatibility checks - update ArrayList compatibility logic to support Zig 0.15+ - adjust version checks from 0.16 to 0.15 for managed/unmanaged ArrayList - update webui dependency to latest commit in build.zig.zon --- build.zig.zon | 4 ++-- examples/event_handling/main.zig | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index b8b209f..a08287a 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,8 +5,8 @@ .minimum_zig_version = "0.14.0", .dependencies = .{ .webui = .{ - .hash = "webui-2.5.0-beta.4-pxqD5UtONwCfX1N9d0zUHHT3igVMHYd6KnI87tg0vyA7", - .url = "https://github.com/webui-dev/webui/archive/897a4406e5e6bdff135a48848343b3b0b983fa75.tar.gz", + .hash = "webui-2.5.0-beta.4-pxqD5ahSNwAE_vnS170oThHZ3blPcHQ85Ut2XHf65f1u", + .url = "https://github.com/webui-dev/webui/archive/dcc776a0f6bdf244d0024042253ebd194271ac9b.tar.gz", }, }, .paths = .{ diff --git a/examples/event_handling/main.zig b/examples/event_handling/main.zig index 5989084..c2ac250 100644 --- a/examples/event_handling/main.zig +++ b/examples/event_handling/main.zig @@ -27,7 +27,7 @@ fn ensureContextsInitialized() void { } if (online_users == null) { // Version compatibility: Zig 0.14/0.15 use managed ArrayList, 0.16+ use unmanaged - if (comptime builtin.zig_version.minor >= 16) { + if (comptime builtin.zig_version.minor >= 15) { // Zig 0.16+ - ArrayList is unmanaged by default online_users = std.ArrayList(OnlineUser){}; } else { @@ -223,7 +223,7 @@ fn userLogin(e: *webui.Event) void { ensureContextsInitialized(); if (online_users) |*users| { // Version compatibility for append method - if (comptime builtin.zig_version.minor >= 16) { + if (comptime builtin.zig_version.minor >= 15) { // Zig 0.16+ - unmanaged ArrayList needs allocator parameter users.append(allocator, OnlineUser{ .client_id = e.client_id, .username = context.username }) catch { std.debug.print("Failed to add user to online list\n", .{}); From 86047f1c9dda960c624fa273c32e41c03861ad08 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 24 Aug 2025 11:50:38 +0800 Subject: [PATCH 5/8] fix(examples): improve advanced window controls and icon handling - update window icon to use raw SVG string instead of base64 - refine window hide/show logic to use minimize/maximize for better UX - replace window.destroy with window.close for proper resource management - add missing charset meta tag and webui.js script to HTML --- examples/advanced_window/index.html | 2 ++ examples/advanced_window/main.zig | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/examples/advanced_window/index.html b/examples/advanced_window/index.html index f3efb40..cfe61cf 100644 --- a/examples/advanced_window/index.html +++ b/examples/advanced_window/index.html @@ -1,6 +1,8 @@ + + Advanced Window Controls - - -
-

⚠️ Error Handling Demonstration

- -
- ℹ️ About This Example:
- This example demonstrates proper error handling in WebUI applications. - Each test below intentionally triggers different types of errors to show - how to handle them gracefully. -
- -
-

Show Window Error

-

Attempts to show a non-existent file to trigger an error.

- -
Click to test...
-
- -
-

Port Configuration Error

-

Attempts to set a restricted port (port 1) to trigger an error.

- -
Click to test...
-
- -
-

Script Execution Error

-

Attempts to execute invalid JavaScript code.

- -
Click to test...
-
- -
-

Encoding/Decoding Error

-

Tests encoding and attempts to decode invalid base64 data.

- -
Click to test...
-
- -
-

Window ID Error

-

Attempts to create a window with an invalid ID (0).

- -
Click to test...
-
- -
-

Get Last Error Information

-

Retrieves the last error information from WebUI.

- -
Click to get last error...
-
- -
-

Timeout Configuration

-

Shows current timeout configuration.

- -
Click to check...
-
- -
- ⚠️ Note: Some errors are expected and demonstrate proper error handling. -
-
- - - - \ No newline at end of file diff --git a/examples/error_handling/main.zig b/examples/error_handling/main.zig deleted file mode 100644 index b7ee4d2..0000000 --- a/examples/error_handling/main.zig +++ /dev/null @@ -1,207 +0,0 @@ -const std = @import("std"); -const webui = @import("webui"); - -pub fn main() !void { - // Demonstrate error handling throughout the application - std.debug.print("Starting Error Handling Example\n", .{}); - - // Set a timeout for connection (10 seconds) - webui.setTimeout(10); - - const window = webui.newWindow(); - defer window.destroy(); - - // Bind error demonstration functions - _ = try window.bind("test_show_error", testShowError); - _ = try window.bind("test_port_error", testPortError); - _ = try window.bind("test_script_error", testScriptError); - _ = try window.bind("test_encode_error", testEncodeError); - _ = try window.bind("test_window_id_error", testWindowIdError); - _ = try window.bind("get_last_error", getLastErrorInfo); - _ = try window.bind("test_timeout", testTimeout); - - // Show the main window - window.show("index.html") catch |err| { - std.debug.print("Failed to show window: {}\n", .{err}); - const error_info = webui.getLastError(); - std.debug.print("WebUI Error #{}: {s}\n", .{ error_info.num, error_info.msg }); - return err; - }; - - webui.wait(); -} - -fn testShowError(e: *webui.Event) void { - const window = e.getWindow(); - - // Try to show with invalid content (simulate error) - window.show("nonexistent_file_that_does_not_exist.html") catch |err| { - const error_info = webui.getLastError(); - - var buffer: [512]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buffer); - const writer = fbs.writer(); - - writer.print("Show Error: {}\nWebUI Error #{}: {s}", .{ err, error_info.num, error_info.msg }) catch {}; - - const written = fbs.getWritten(); - var null_terminated: [513]u8 = undefined; - @memcpy(null_terminated[0..written.len], written); - null_terminated[written.len] = 0; - - e.returnString(null_terminated[0..written.len :0]); - return; - }; - - e.returnString("No error occurred"); -} - -fn testPortError(e: *webui.Event) void { - const window = e.getWindow(); - - // Try to set an invalid port (port 1 is usually restricted) - window.setPort(1) catch |err| { - const error_info = webui.getLastError(); - - var buffer: [512]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buffer); - const writer = fbs.writer(); - - writer.print("Port Error: {}\nWebUI Error #{}: {s}", .{ err, error_info.num, error_info.msg }) catch {}; - - const written = fbs.getWritten(); - var null_terminated: [513]u8 = undefined; - @memcpy(null_terminated[0..written.len], written); - null_terminated[written.len] = 0; - - e.returnString(null_terminated[0..written.len :0]); - return; - }; - - e.returnString("Port set successfully (unexpected)"); -} - -fn testScriptError(e: *webui.Event) void { - const window = e.getWindow(); - - // Try to execute invalid JavaScript - var result_buffer: [256]u8 = undefined; - window.script("this_is_invalid_javascript()", 1000, &result_buffer) catch |err| { - const error_info = webui.getLastError(); - - var buffer: [512]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buffer); - const writer = fbs.writer(); - - writer.print("Script Error: {}\nWebUI Error #{}: {s}", .{ err, error_info.num, error_info.msg }) catch {}; - - const written = fbs.getWritten(); - var null_terminated: [513]u8 = undefined; - @memcpy(null_terminated[0..written.len], written); - null_terminated[written.len] = 0; - - e.returnString(null_terminated[0..written.len :0]); - return; - }; - - e.returnString("Script executed (unexpected)"); -} - -fn testEncodeError(e: *webui.Event) void { - // Test encoding with proper error handling - const test_string = "Test encoding string"; - - const encoded = webui.encode(test_string) catch |err| { - var buffer: [256]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buffer); - const writer = fbs.writer(); - - writer.print("Encode Error: {}", .{err}) catch {}; - - const written = fbs.getWritten(); - var null_terminated: [257]u8 = undefined; - @memcpy(null_terminated[0..written.len], written); - null_terminated[written.len] = 0; - - e.returnString(null_terminated[0..written.len :0]); - return; - }; - defer webui.free(encoded); - - // Try to decode invalid base64 - const invalid_base64 = "This is not valid base64!!!"; - const decoded = webui.decode(invalid_base64) catch |err| { - var buffer: [256]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buffer); - const writer = fbs.writer(); - - writer.print("Decode Error: {} (Expected for invalid base64)", .{err}) catch {}; - - const written = fbs.getWritten(); - var null_terminated: [257]u8 = undefined; - @memcpy(null_terminated[0..written.len], written); - null_terminated[written.len] = 0; - - e.returnString(null_terminated[0..written.len :0]); - return; - }; - defer webui.free(decoded); - - e.returnString("No errors occurred"); -} - -fn testWindowIdError(e: *webui.Event) void { - // Try to create window with invalid ID - const invalid_window = webui.newWindowWithId(0) catch |err| { - var buffer: [256]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buffer); - const writer = fbs.writer(); - - writer.print("Window ID Error: {} (ID 0 is invalid)", .{err}) catch {}; - - const written = fbs.getWritten(); - var null_terminated: [257]u8 = undefined; - @memcpy(null_terminated[0..written.len], written); - null_terminated[written.len] = 0; - - e.returnString(null_terminated[0..written.len :0]); - return; - }; - - invalid_window.destroy(); - e.returnString("Window created (unexpected)"); -} - -fn getLastErrorInfo(e: *webui.Event) void { - const error_info = webui.getLastError(); - - var buffer: [512]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buffer); - const writer = fbs.writer(); - - writer.print("Last Error Information:\nError Number: {}\nError Message: {s}", .{ error_info.num, error_info.msg }) catch {}; - - const written = fbs.getWritten(); - var null_terminated: [513]u8 = undefined; - @memcpy(null_terminated[0..written.len], written); - null_terminated[written.len] = 0; - - e.returnString(null_terminated[0..written.len :0]); -} - -fn testTimeout(e: *webui.Event) void { - // Demonstrate timeout configuration - var buffer: [256]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buffer); - const writer = fbs.writer(); - - writer.print("Current timeout is set to 10 seconds.\nSet to 0 for infinite wait.", .{}) catch {}; - - const written = fbs.getWritten(); - var null_terminated: [257]u8 = undefined; - @memcpy(null_terminated[0..written.len], written); - null_terminated[written.len] = 0; - - e.returnString(null_terminated[0..written.len :0]); -} - diff --git a/examples/secure_communication/index.html b/examples/secure_communication/index.html deleted file mode 100644 index 6217626..0000000 --- a/examples/secure_communication/index.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - Secure Communication Example - - - -
-

🔒 Secure Communication & Encoding

- -
-

Base64 Encoding

- - -
-
- -
-

Base64 Decoding

- - -
-
- -
-

Binary Data Transfer

-

Send and receive raw binary data between backend and frontend.

- -
Waiting for binary data...
-
- -
-

Memory Management Test

-

Test WebUI's memory allocation and management functions.

- -
-
-
- - - - \ No newline at end of file diff --git a/examples/secure_communication/main.zig b/examples/secure_communication/main.zig deleted file mode 100644 index 57a4caa..0000000 --- a/examples/secure_communication/main.zig +++ /dev/null @@ -1,117 +0,0 @@ -const std = @import("std"); -const webui = @import("webui"); - -pub fn main() !void { - const window = webui.newWindow(); - defer window.destroy(); - - // Set up TLS certificate (only works with webui-2-secure library) - // In production, use real certificates - // webui.setTlsCertificate("", "") catch { - // std.debug.print("TLS not supported in this build\n", .{}); - // }; - - // Bind events for secure communication - _ = try window.bind("encode_data", encodeData); - _ = try window.bind("decode_data", decodeData); - _ = try window.bind("send_binary", sendBinaryData); - _ = try window.bind("test_memory", testMemoryManagement); - - // Show the window - try window.show("index.html"); - - // Wait for the window to close - webui.wait(); -} - -fn encodeData(e: *webui.Event) void { - // Get the data to encode from JavaScript - const data = e.getString(); - - std.debug.print("Encoding data: {s}\n", .{data}); - - // Encode the data using WebUI's base64 encoding - const encoded = webui.encode(data) catch |err| { - std.debug.print("Encoding failed: {}\n", .{err}); - e.returnString("Encoding failed"); - return; - }; - defer webui.free(encoded); - - // Create null-terminated string for return - var result_buffer: [1024]u8 = undefined; - const len = @min(encoded.len, result_buffer.len - 1); - @memcpy(result_buffer[0..len], encoded[0..len]); - result_buffer[len] = 0; - - e.returnString(result_buffer[0..len :0]); -} - -fn decodeData(e: *webui.Event) void { - // Get the base64 data to decode - const encoded_data = e.getString(); - - std.debug.print("Decoding data: {s}\n", .{encoded_data}); - - // Decode the data - const decoded = webui.decode(encoded_data) catch |err| { - std.debug.print("Decoding failed: {}\n", .{err}); - e.returnString("Decoding failed"); - return; - }; - defer webui.free(decoded); - - // Create null-terminated string for return - var result_buffer: [1024]u8 = undefined; - const len = @min(decoded.len, result_buffer.len - 1); - @memcpy(result_buffer[0..len], decoded[0..len]); - result_buffer[len] = 0; - - e.returnString(result_buffer[0..len :0]); -} - -fn sendBinaryData(e: *webui.Event) void { - const window = e.getWindow(); - - // Create some binary data - var binary_data = [_]u8{ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21 }; - - // Send raw binary data to the UI - window.sendRaw("receiveBinaryData", &binary_data); - - // Also demonstrate sending to specific client - e.sendRawClient("receiveClientData", &binary_data); - - e.returnString("Binary data sent"); -} - -fn testMemoryManagement(e: *webui.Event) void { - // Demonstrate WebUI's memory management - - // Allocate memory using WebUI's allocator - const size: usize = 256; - const buffer = webui.malloc(size) catch |err| { - std.debug.print("Memory allocation failed: {}\n", .{err}); - e.returnString("Memory allocation failed"); - return; - }; - defer webui.free(buffer); - - // Fill buffer with test data - const test_data = "This is test data for memory management"; - const copy_len = @min(test_data.len, buffer.len); - webui.memcpy(buffer[0..copy_len], test_data[0..copy_len]); - - // Create another buffer and copy data - const buffer2 = webui.malloc(size) catch { - e.returnString("Second allocation failed"); - return; - }; - defer webui.free(buffer2); - - webui.memcpy(buffer2[0..copy_len], buffer[0..copy_len]); - - // Return the copied data - buffer2[copy_len] = 0; - e.returnString(buffer2[0..copy_len :0]); -} From de7ef74cb1073a42bfb4f078bfdc268c65845274 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 24 Aug 2025 21:00:39 +0800 Subject: [PATCH 8/8] fix(examples): remove browser profiles demo - delete browser_profiles example including HTML and Zig files - remove profile management and browser detection sample code --- examples/browser_profiles/index.html | 104 ------------------------- examples/browser_profiles/main.zig | 93 ---------------------- examples/browser_profiles/profile.html | 35 --------- 3 files changed, 232 deletions(-) delete mode 100644 examples/browser_profiles/index.html delete mode 100644 examples/browser_profiles/main.zig delete mode 100644 examples/browser_profiles/profile.html diff --git a/examples/browser_profiles/index.html b/examples/browser_profiles/index.html deleted file mode 100644 index e1e8f4b..0000000 --- a/examples/browser_profiles/index.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - Browser Profiles Example - - - - -
-

Browser Profiles Management

- -
-

Current Profile

-

This window is using the default browser profile.

-

Custom parameters: --disable-gpu --disable-dev-shm-usage

-
- -
-

Profile Actions

- - - - -
- -
-
- - - - \ No newline at end of file diff --git a/examples/browser_profiles/main.zig b/examples/browser_profiles/main.zig deleted file mode 100644 index 5ab1c6e..0000000 --- a/examples/browser_profiles/main.zig +++ /dev/null @@ -1,93 +0,0 @@ -const std = @import("std"); -const webui = @import("webui"); - -pub fn main() !void { - // Initialize windows - const main_window = webui.newWindow(); - defer main_window.destroy(); - - const profile_window = webui.newWindow(); - defer profile_window.destroy(); - - // Set custom browser folder if needed (optional) - // webui.setBrowserFolder("C:\\Program Files\\Google\\Chrome\\Application"); - - // Check if browsers exist - std.debug.print("Chrome exists: {}\n", .{webui.browserExist(.Chrome)}); - std.debug.print("Firefox exists: {}\n", .{webui.browserExist(.Firefox)}); - std.debug.print("Edge exists: {}\n", .{webui.browserExist(.Edge)}); - - // Get the best browser available - const best_browser = main_window.getBestBrowser(); - std.debug.print("Best browser: {}\n", .{best_browser}); - - // Set up the main window with default profile - main_window.setProfile("", ""); - - // Set proxy for the main window (optional) - // main_window.setProxy("http://proxy.example.com:8080"); - - // Set custom browser parameters - main_window.setCustomParameters("--disable-gpu --disable-dev-shm-usage"); - - // Bind events - _ = try main_window.bind("switch_profile", switchProfile); - _ = try main_window.bind("delete_profile", deleteCurrentProfile); - _ = try main_window.bind("show_info", showBrowserInfo); - - // Show main window - try main_window.show("index.html"); - - // Set up profile window with custom profile - profile_window.setProfile("TestProfile", ""); - - // Show profile window with different browser - try profile_window.showBrowser("profile.html", .Firefox); - // Wait for windows to close - webui.wait(); - - // Clean up profiles at the end - main_window.deleteProfile(); - profile_window.deleteProfile(); - - // Optionally delete all profiles - // webui.deleteAllProfiles(); -} - -fn switchProfile(e: *webui.Event) void { - // Get profile name from JavaScript - const profile_name = e.getString(); - - std.debug.print("Switching to profile: {s}\n", .{profile_name}); - - // Note: In a real application, you would close and reopen the window - // with the new profile since profiles can't be changed after show() - e.returnString("Profile switch requested. Please restart the window."); -} -fn deleteCurrentProfile(e: *webui.Event) void { - std.debug.print("Deleting current profile...\n", .{}); - - // This will delete the profile folder when the window closes - e.getWindow().deleteProfile(); - - e.returnString("Profile will be deleted when window closes."); -} -fn showBrowserInfo(e: *webui.Event) void { - var info_buffer: [1024]u8 = undefined; - var fbs = std.io.fixedBufferStream(&info_buffer); - const writer = fbs.writer(); - - writer.print("Browser Detection Info:\n", .{}) catch {}; - writer.print("Chrome: {}\n", .{webui.browserExist(.Chrome)}) catch {}; - writer.print("Firefox: {}\n", .{webui.browserExist(.Firefox)}) catch {}; - writer.print("Edge: {}\n", .{webui.browserExist(.Edge)}) catch {}; - writer.print("Safari: {}\n", .{webui.browserExist(.Safari)}) catch {}; - writer.print("Chromium: {}\n", .{webui.browserExist(.Chromium)}) catch {}; - writer.print("Best Browser: {}\n", .{e.getWindow().getBestBrowser()}) catch {}; - const written = fbs.getWritten(); - var null_terminated: [1025]u8 = undefined; - @memcpy(null_terminated[0..written.len], written); - null_terminated[written.len] = 0; - - e.returnString(null_terminated[0..written.len :0]); -} diff --git a/examples/browser_profiles/profile.html b/examples/browser_profiles/profile.html deleted file mode 100644 index f39e421..0000000 --- a/examples/browser_profiles/profile.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - Test Profile Window - - - -
-

Test Profile Window

-

This window is using a custom profile: TestProfile

-

It was opened with Firefox browser preference.

-

Profile data is isolated from the main window.

-
- - \ No newline at end of file