@@ -1,4 +1,5 @@ 1 1 const std = ; 2 + const desktop_script_encoding = ; 2 3 const desktop_window_registry = ; 3 4 const window_context = ; 4 5 const window_reply_target = ; ⋯ 78 lines Show the 78 lines above this hunk @@ -83,6 +84,7 @@ pub const SW_MINIMIZE: c_int = 6; 83 84 pub const WM_DESTROY: UINT = 0x0002; 84 85 pub const WM_SIZE: UINT = 0x0005; 85 86 pub const WM_CLOSE: UINT = 0x0010; 87 + pub const WM_QUIT: UINT = 0x0012; 86 88 pub const PM_REMOVE: UINT = 0x0001; 87 89 pub const GWLP_USERDATA: c_int = -21; 88 90 ⋯ 561 lines Show the 561 lines above this hunk @@ -650,6 +652,57 @@ const ExecuteScriptCompletedHandler = extern struct { 650 652 651 653 pub const ICoreWebView2ExecuteScriptCompletedHandler = ExecuteScriptCompletedHandler; 652 654 655 + // -- Document-start script completed handler --------------------------------- 656 + 657 + const ScriptInstallContext = struct { 658 + done: bool = false, 659 + result: HRESULT = -1, 660 + }; 661 + 662 + const ScriptInstallCompletedHandler = extern struct { 663 + lpVtbl: *const ScriptInstallCompletedHandlerVtbl, 664 + ref_count: c_ulong, 665 + ctx: *ScriptInstallContext, 666 + 667 + const ScriptInstallCompletedHandlerVtbl = extern struct { 668 + QueryInterface: *const fn (*ScriptInstallCompletedHandler, *const GUID, *?*anyopaque) callconv(.c) HRESULT, 669 + AddRef: *const fn (*ScriptInstallCompletedHandler) callconv(.c) c_ulong, 670 + Release: *const fn (*ScriptInstallCompletedHandler) callconv(.c) c_ulong, 671 + Invoke: *const fn (*ScriptInstallCompletedHandler, HRESULT, LPCWSTR) callconv(.c) HRESULT, 672 + }; 673 + 674 + const vtbl_instance = ScriptInstallCompletedHandlerVtbl{ 675 + .QueryInterface = &queryInterface, 676 + .AddRef = &addRef, 677 + .Release = &release, 678 + .Invoke = &invoke, 679 + }; 680 + 681 + fn queryInterface(self: *ScriptInstallCompletedHandler, _: *const GUID, ppv: *?*anyopaque) callconv(.c) HRESULT { 682 + ppv.* = @ptrCast(self); 683 + _ = addRef(self); 684 + return S_OK; 685 + } 686 + 687 + fn addRef(self: *ScriptInstallCompletedHandler) callconv(.c) c_ulong { 688 + self.ref_count += 1; 689 + return self.ref_count; 690 + } 691 + 692 + fn release(self: *ScriptInstallCompletedHandler) callconv(.c) c_ulong { 693 + if (self.ref_count > 0) self.ref_count -= 1; 694 + const remaining = self.ref_count; 695 + if (remaining == 0) std.heap.c_allocator.destroy(self); 696 + return remaining; 697 + } 698 + 699 + fn invoke(self: *ScriptInstallCompletedHandler, hr: HRESULT, _: LPCWSTR) callconv(.c) HRESULT { 700 + self.ctx.result = hr; 701 + self.ctx.done = true; 702 + return S_OK; 703 + } 704 + }; 705 + 653 706 // ============================================================================ 654 707 // WebView2Loader — loaded dynamically at runtime to avoid link-time dependency 655 708 // ============================================================================ ⋯ 207 lines Show the 207 lines above this hunk @@ -863,7 +916,7 @@ pub const Window = struct { 863 916 return error.TooManyWindows; 864 917 }; 865 918 866 - const window = Window{ 919 + var window = Window{ 867 920 .id = window_id, 868 921 .hwnd = hwnd, 869 922 .controller = controller, ⋯ 5 lines Show the 5 lines above this hunk @@ -875,6 +928,9 @@ pub const Window = struct { 875 928 .y = y, 876 929 }; 877 930 931 + // Install before the first Navigate/NavigateToString so every page 932 + // starts with the same bridge surface as a macOS Craft window. 933 + try window.injectScript(@embedFile("js/craft-bridge.js")); 878 934 return window; 879 935 } 880 936 ⋯ 123 lines Show the 123 lines above this hunk @@ -1004,14 +1060,37 @@ pub const Window = struct { 1004 1060 pub fn injectScript(self: *Window, script: []const u8) !void { 1005 1061 const entry = self.liveEntry() orelse return error.NoWebView; 1006 1062 const webview: *ICoreWebView2 = @ptrFromInt(entry.webview); 1007 - var script_wide = try utf8ToUtf16Z ( 16384 , script); 1008 - const script_ptr: LPCWSTR = @ptrCast(& script_wide); 1009 - 1010 - const hr = webview.lpVtbl.AddScriptToExecuteOnDocumentCreated(webview, script_ptr, null); 1063 + var script_wide = try desktop_script_encoding.encode ( std.heap.c_allocator , script); 1064 + defer script_wide .deinit(std.heap.c_allocator ); 1065 + 1066 + var install: ScriptInstallContext = .{}; 1067 + const handler = try std.heap.c_allocator.create(ScriptInstallCompletedHandler); 1068 + handler.* = .{ .lpVtbl = &ScriptInstallCompletedHandler.vtbl_instance, .ref_count = 1, .ctx = &install }; 1069 + const hr = webview.lpVtbl.AddScriptToExecuteOnDocumentCreated(webview, script_wide.ptr(), handler); 1070 + _ = ScriptInstallCompletedHandler.release(handler); 1011 1071 if (!succeeded(hr)) { 1012 1072 std.debug.print("[WebView2] AddScriptToExecuteOnDocumentCreated failed: 0x{x}\n", .{@as(u32, @bitCast(hr))}); 1013 1073 return error.ScriptInjectionFailed; 1014 1074 } 1075 + 1076 + // WebView2 installs this asynchronously. Navigate immediately after 1077 + // the API call and the first page can miss the bridge entirely. 1078 + var msg: MSG = undefined; 1079 + var quit_requested = false; 1080 + while (!install.done) { 1081 + if (PeekMessageW(&msg, null, 0, 0, PM_REMOVE) != 0) { 1082 + if (msg.message == WM_QUIT) { 1083 + quit_requested = true; 1084 + } else { 1085 + _ = TranslateMessage(&msg); 1086 + _ = DispatchMessageW(&msg); 1087 + } 1088 + } else { 1089 + Sleep(1); 1090 + } 1091 + } 1092 + if (quit_requested) PostQuitMessage(0); 1093 + if (!succeeded(install.result)) return error.ScriptInjectionFailed; 1015 1094 } 1016 1095 1017 1096 pub fn enableGPUAcceleration(self: *Window, enable: bool) !void {