ReviewOS

also looking at this

craft-native/craft

feat: finish native customization and platform seams

#401
Merged glennmichael123 wants to merge codex/issues-388-400-344 into main
21 files +707 -17

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/src/macos.zigmodified+68-0
Changes to packages/zig/src/macos.zig
@@ -6509,10 +6509,66 @@ fn handleCreateWebView(
65096509 return null;
65106510}
65116511
6512/// `webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:`
6513///
6514/// WKWebView does not present an `NSOpenPanel` for an HTML file input on its
6515/// own. Without this delegate callback the input is inert, including the
6516/// hidden-input-and-click pattern used by upload controls. Keep the panel
6517/// synchronous, like Craft's other AppKit file dialogs, so the completion
6518/// block remains valid until WebKit receives the selected URLs (or nil on
6519/// cancellation).
6520fn handleOpenPanel(
6521 _: objc.id,
6522 _: objc.SEL,
6523 webView: objc.id,
6524 parameters: objc.id,
6525 frame: objc.id,
6526 completionHandler: objc.id,
6527) callconv(.c) void {
6528 _ = webView;
6529 _ = frame;
6530
6531 const handler = completionHandler orelse return;
6532
6533 const panel = msgSend0(getClass("NSOpenPanel"), "openPanel");
6534 if (panel == null) {
6535 const BlockLayout = extern struct {
6536 isa: ?*anyopaque,
6537 flags: c_int,
6538 reserved: c_int,
6539 invoke: *const fn (*anyopaque, objc.id) callconv(.c) void,
6540 };
6541 const block: *BlockLayout = @ptrCast(@alignCast(handler));
6542 block.invoke(handler, null);
6543 return;
6544 }
6545
6546 _ = msgSend1Bool(panel, "setCanChooseFiles:", true);
6547 _ = msgSend1Bool(panel, "setCanChooseDirectories:", parameters != null and msgSendBool(parameters, "allowsDirectories"));
6548 _ = msgSend1Bool(panel, "setAllowsMultipleSelection:", parameters != null and msgSendBool(parameters, "allowsMultipleSelection"));
6549
6550 if (parameters != null and msgSendBool1Sel(parameters, "respondsToSelector:", sel("allowedFileExtensions"))) {
6551 const extensions = msgSend0(parameters, "allowedFileExtensions");
6552 if (extensions != null) _ = msgSend1(panel, "setAllowedFileTypes:", extensions);
6553 }
6554
6555 const response = msgSend0(panel, "runModal");
6556 const urls: objc.id = if (@intFromPtr(response) == 1) msgSend0(panel, "URLs") else null;
6557 const BlockLayout = extern struct {
6558 isa: ?*anyopaque,
6559 flags: c_int,
6560 reserved: c_int,
6561 invoke: *const fn (*anyopaque, objc.id) callconv(.c) void,
6562 };
6563 const block: *BlockLayout = @ptrCast(@alignCast(handler));
6564 block.invoke(handler, urls);
6565}
6566
65126567/// Attach craft's `WKUIDelegate`.
65136568///
65146569/// Two callbacks, neither of which has anything to do with the other:
65156570/// `getUserMedia()` permission, and the page asking for a second window.
6571/// The third callback supplies the AppKit panel behind HTML file inputs.
65166572///
65176573/// Installed in every non-benchmark window. It used to be installed only under
65186574/// `--dev-tools`, which meant a packaged app was denied the camera outright
@@ -6563,6 +6619,18 @@ pub fn setupUIDelegate(webview: objc.id) !void {
65636619 }
65646620 }
65656621
6622 // HTML `<input type="file">`. The completion block receives an
6623 // NSArray<NSURL *> or nil when the person cancels the panel.
6624 if (!objc.class_addMethod(
6625 @ptrCast(@alignCast(delegateClass)),
6626 objc.sel_registerName("webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:"),
6627 @as(objc.IMP, @ptrCast(@constCast(&handleOpenPanel))),
6628 "v@:@@@?",
6629 )) {
6630 if (comptime std.ascii.eqlIgnoreCase(@tagName(builtin.mode), "debug"))
6631 std.debug.print("[UI] Failed to add file open panel method\n", .{});
6632 }
6633
65666634 // `window.open()` / `target="_blank"`. Returns an object (the child
65676635 // webview, or nil): @ = id return, self, _cmd, webView, configuration,
65686636 // navigationAction, windowFeatures.
packages/zig/test/window_lifecycle_test.zigmodified+18-0
Changes to packages/zig/test/window_lifecycle_test.zig
@@ -1003,6 +1003,24 @@ test "webview delegate ownership ends with its webview" {
10031003 try testing.expect(callsFunction(handler_body, "msgSendVoid0(handler, \"release\")"));
10041004}
10051005
1006test "the UI delegate exposes the WebKit file-input panel" {
1007 const setup_start = std.mem.indexOf(u8, macos_source, "pub fn setupUIDelegate(") orelse
1008 return error.WebViewDelegateSetupNotFound;
1009 const setup_body = enclosingFnBody(macos_source, setup_start);
1010 try testing.expect(std.mem.indexOf(u8, setup_body, "webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:") != null);
1011 try testing.expect(std.mem.indexOf(u8, setup_body, "v@:@@@?") != null);
1012
1013 const panel_start = std.mem.indexOf(u8, macos_source, "fn handleOpenPanel(") orelse
1014 return error.OpenPanelHandlerNotFound;
1015 const panel_body = enclosingFnBody(macos_source, panel_start);
1016 try testing.expect(callsFunction(panel_body, "openPanel"));
1017 try testing.expect(callsFunction(panel_body, "setAllowsMultipleSelection:"));
1018 try testing.expect(callsFunction(panel_body, "setAllowedFileTypes:"));
1019 try testing.expect(callsFunction(panel_body, "runModal"));
1020 try testing.expect(std.mem.indexOf(u8, panel_body, "block.invoke(handler, urls)") != null);
1021 try testing.expect(std.mem.indexOf(u8, panel_body, "else null") != null);
1022}
1023
10061024test "the scan can actually find the constructors it claims to check" {
10071025 // Guards the guard: every test above depends on `enclosingFnBody` locating
10081026 // real function bodies. If the file's formatting changes such that it