also looking at this
feat: add Linux and Windows named-window routing
#325
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.
| @@ -1247,6 +1247,14 @@ pub fn build(b: *std.Build) void { | ||
| 1247 | 1247 | }), |
| 1248 | 1248 | }); |
| 1249 | 1249 | |
| 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 | ||
| 1250 | 1258 | // The shell bridge's argv construction: what a `spawn` actually runs. |
| 1251 | 1259 | // Untested until now, which is how it shipped parsing an `args` array and |
| 1252 | 1260 | // throwing it away. |
| @@ -1700,6 +1708,7 @@ pub fn build(b: *std.Build) void { | ||
| 1700 | 1708 | const run_window_lifecycle_tests = b.addRunArtifact(window_lifecycle_tests); |
| 1701 | 1709 | const run_window_registry_tests = b.addRunArtifact(window_registry_tests); |
| 1702 | 1710 | const run_window_context_tests = b.addRunArtifact(window_context_tests); |
| 1711 | const run_window_reply_target_tests = b.addRunArtifact(window_reply_target_tests); | |
| 1703 | 1712 | const run_external_link_tests = b.addRunArtifact(external_link_tests); |
| 1704 | 1713 | const run_webview_recovery_tests = b.addRunArtifact(webview_recovery_tests); |
| 1705 | 1714 | const run_request_context_tests = b.addRunArtifact(request_context_tests); |
| @@ -1848,6 +1857,7 @@ pub fn build(b: *std.Build) void { | ||
| 1848 | 1857 | test_step.dependOn(&run_window_lifecycle_tests.step); |
| 1849 | 1858 | test_step.dependOn(&run_window_registry_tests.step); |
| 1850 | 1859 | test_step.dependOn(&run_window_context_tests.step); |
| 1860 | test_step.dependOn(&run_window_reply_target_tests.step); | |
| 1851 | 1861 | test_step.dependOn(&run_external_link_tests.step); |
| 1852 | 1862 | test_step.dependOn(&run_webview_recovery_tests.step); |
| 1853 | 1863 | test_step.dependOn(&run_request_context_tests.step); |
| @@ -1,4 +1,6 @@ | ||
| 1 | 1 | const std = ; |
| 2 | const window_context = ; | |
| 3 | const window_reply_target = ; | |
| 2 | 4 | |
| 3 | 5 | // Linux implementation using GTK3 and WebKit2GTK 4.1. |
| 4 | 6 | // Requires: libgtk-3-dev, libwebkit2gtk-4.1-dev |
| @@ -489,28 +491,29 @@ pub const App = struct { | ||
| 489 | 491 | } |
| 490 | 492 | }; |
| 491 | 493 | |
| 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. | |
| 494 | 496 | pub 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; | |
| 496 | 499 | var latest_id: u32 = 0; |
| 497 | var latest_webview: ?*anyopaque = null; | |
| 500 | var fallback: ?usize = null; | |
| 498 | 501 | for (window_registry) |entry| { |
| 499 | 502 | if (entry) |e| { |
| 503 | live[live_len] = @intFromPtr(e.webview); | |
| 504 | live_len += 1; | |
| 500 | 505 | if (e.id > latest_id) { |
| 501 | 506 | latest_id = e.id; |
| 502 | latest_webview = e.webview; | |
| 507 | fallback = @intFromPtr(e.webview); | |
| 503 | 508 | } |
| 504 | 509 | } |
| 505 | 510 | } |
| 506 | 511 | |
| 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 | |
| 512 | 513 | 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); | |
| 514 | 517 | } |
| 515 | 518 | |
| 516 | 519 | // Legacy API compatibility |
| @@ -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 | ||
| 8 | const std = ; | |
| 9 | ||
| 10 | pub 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 | ||
| 19 | test "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 | ||
| 24 | test "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 | ||
| 29 | test "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 | } | |