ReviewOS

also looking at this

craft-native/craft

feat: add Linux and Windows named-window routing

#325
Merged glennmichael123 wants to merge codex/multi-window-linux-windows into main
29 files +2250 -200

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

packages/zig/build.zigmodified+10-0
Changes to packages/zig/build.zig
@@ -1247,6 +1247,14 @@ pub fn build(b: *std.Build) void {
12471247 }),
12481248 });
12491249
1250 const window_reply_target_tests = b.addTest(.{
1251 .root_module = b.createModule(.{
1252 .root_source_file = b.path("src/window_reply_target.zig"),
1253 .target = target,
1254 .optimize = optimize,
1255 }),
1256 });
1257
12501258 // The shell bridge's argv construction: what a `spawn` actually runs.
12511259 // Untested until now, which is how it shipped parsing an `args` array and
12521260 // throwing it away.
@@ -1700,6 +1708,7 @@ pub fn build(b: *std.Build) void {
17001708 const run_window_lifecycle_tests = b.addRunArtifact(window_lifecycle_tests);
17011709 const run_window_registry_tests = b.addRunArtifact(window_registry_tests);
17021710 const run_window_context_tests = b.addRunArtifact(window_context_tests);
1711 const run_window_reply_target_tests = b.addRunArtifact(window_reply_target_tests);
17031712 const run_external_link_tests = b.addRunArtifact(external_link_tests);
17041713 const run_webview_recovery_tests = b.addRunArtifact(webview_recovery_tests);
17051714 const run_request_context_tests = b.addRunArtifact(request_context_tests);
@@ -1848,6 +1857,7 @@ pub fn build(b: *std.Build) void {
18481857 test_step.dependOn(&run_window_lifecycle_tests.step);
18491858 test_step.dependOn(&run_window_registry_tests.step);
18501859 test_step.dependOn(&run_window_context_tests.step);
1860 test_step.dependOn(&run_window_reply_target_tests.step);
18511861 test_step.dependOn(&run_external_link_tests.step);
18521862 test_step.dependOn(&run_webview_recovery_tests.step);
18531863 test_step.dependOn(&run_request_context_tests.step);
packages/zig/src/linux.zigmodified+14-11
Changes to packages/zig/src/linux.zig
@@ -1,4 +1,6 @@
11const std = ;
2const window_context = ;
3const window_reply_target = ;
24
35// Linux implementation using GTK3 and WebKit2GTK 4.1.
46// Requires: libgtk-3-dev, libwebkit2gtk-4.1-dev
@@ -489,28 +491,29 @@ pub const App = struct {
489491 }
490492};
491493
492/// Evaluate JavaScript in the current webview (cross-platform bridge helper).
493/// Uses the most recently registered window's webview from the window registry.
494/// Evaluate JavaScript in the page that sent the current bridge message.
495/// Native callers without a sender continue to use the latest live window.
494496pub fn evalJS(script: []const u8) !void {
495 // Find the most recently registered window's webview
497 var live: [window_registry.len]usize = undefined;
498 var live_len: usize = 0;
496499 var latest_id: u32 = 0;
497 var latest_webview: ?*anyopaque = null;
500 var fallback: ?usize = null;
498501 for (window_registry) |entry| {
499502 if (entry) |e| {
503 live[live_len] = @intFromPtr(e.webview);
504 live_len += 1;
500505 if (e.id > latest_id) {
501506 latest_id = e.id;
502 latest_webview = e.webview;
507 fallback = @intFromPtr(e.webview);
503508 }
504509 }
505510 }
506511
507 if (latest_webview) |webview| {
508 const script_z = .dupeZ(std.heap.c_allocator, u8, script) catch return error.OutOfMemory;
509 defer std.heap.c_allocator.free(script_z);
510 webkit_web_view_run_javascript(webview, script_z, null, null, null);
511 } else {
512 const target = window_reply_target.select(live[0..live_len], window_context.currentWebView(), fallback) orelse
512513 return error.NoWebView;
513 }
514 const script_z = try .dupeZ(std.heap.c_allocator, u8, script);
515 defer std.heap.c_allocator.free(script_z);
516 webkit_web_view_run_javascript(@ptrFromInt(target), script_z, null, null, null);
514517}
515518
516519// Legacy API compatibility
packages/zig/src/window_reply_target.zigadded+35-0
Changes to packages/zig/src/window_reply_target.zig
@@ -0,0 +1,35 @@
1//! Select a live desktop webview for a bridge reply.
2//!
3//! A reply made while serving a page belongs to that page. If its webview was
4//! destroyed during a nested run loop, sending the reply to the latest window
5//! would settle somebody else's call instead. Only native callers without a
6//! sender context may use the platform's fallback webview.
7
8const std = ;
9
10pub fn select(live_webviews: []const usize, sender: ?usize, fallback: ?usize) ?usize {
11 const requested = sender orelse fallback orelse return null;
12 if (requested == 0) return null;
13 for (live_webviews) |webview| {
14 if (webview == requested) return webview;
15 }
16 return null;
17}
18
19test "a page reply targets its own live webview" {
20 const live = [_]usize{ 0x1000, 0x2000 };
21 try std.testing.expectEqual(@as(?usize, 0x1000), select(&live, 0x1000, 0x2000));
22}
23
24test "a destroyed sender never falls through to another page" {
25 const live = [_]usize{0x2000};
26 try std.testing.expectEqual(@as(?usize, null), select(&live, 0x1000, 0x2000));
27}
28
29test "a native caller uses only a live fallback" {
30 const live = [_]usize{ 0x1000, 0x2000 };
31 try std.testing.expectEqual(@as(?usize, 0x2000), select(&live, null, 0x2000));
32 try std.testing.expectEqual(@as(?usize, null), select(&live, null, 0x3000));
33 try std.testing.expectEqual(@as(?usize, null), select(&live, null, null));
34 try std.testing.expectEqual(@as(?usize, null), select(&live, null, 0));
35}