From 71af1706581deca4e5839737a29f1c70a58602d0 Mon Sep 17 00:00:00 2001 From: Navid EMAD Date: Wed, 29 Apr 2026 15:01:18 +0200 Subject: [PATCH] cdp: fall back to dialog defaultText when LP.handleJavaScriptDialog promptText is null MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a CDP client pre-arms `LP.handleJavaScriptDialog {accept: true}` with no `promptText` and the page subsequently calls `window.prompt(message, defaultText)`, Lightpanda discarded the dialog's `defaultText` argument and returned `""`. Chrome's behavior is to surface `defaultText` as the prompt's return value — that's the natural "user accepted without typing anything" outcome per the HTML simple-dialog algorithm. The fix is one line in `Window.zig`'s `prompt` JS binding: keep the second argument named (`default_text` instead of `_`) and use it as the fallback. Pre-armed `promptText` still wins when the client supplies it; `accept = false` still returns null regardless. When neither `promptText` nor `defaultText` is provided, the binding still returns `""` per the CDP spec. Adds four new assertions to the existing `cdp.lp` integration test covering the matrix: defaultText fallback (no promptText), promptText overrides defaultText, accept=false ignores defaultText, and the existing no-defaultText case continues to return `""`. --- src/browser/webapi/Window.zig | 9 ++++++--- src/cdp/domains/lp.zig | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index d67b174b..0945dc7f 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -1105,7 +1105,7 @@ pub const JsApi = struct { } }.confirm, .{}); pub const prompt = bridge.function(struct { - fn prompt(_: *const Window, message: ?[]const u8, _: ?[]const u8, frame: *Frame) ?[]const u8 { + fn prompt(_: *const Window, message: ?[]const u8, default_text: ?[]const u8, frame: *Frame) ?[]const u8 { var response: Notification.DialogResponse = .{}; frame._session.notification.dispatch(.javascript_dialog_opening, &.{ .url = frame.url, @@ -1114,9 +1114,12 @@ pub const JsApi = struct { .response = &response, }); if (!response.accept) return null; - // promptText omitted with accept=true is "" per CDP spec + // Pre-armed promptText wins when present. Otherwise fall back to + // the dialog's defaultText (second arg to window.prompt) — Chrome's + // accept-without-typing behavior. If both are absent, return "" + // per CDP spec // (https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-handleJavaScriptDialog). - return response.prompt_text orelse ""; + return response.prompt_text orelse default_text orelse ""; } }.prompt, .{}); diff --git a/src/cdp/domains/lp.zig b/src/cdp/domains/lp.zig index 95a04133..2b042c99 100644 --- a/src/cdp/domains/lp.zig +++ b/src/cdp/domains/lp.zig @@ -569,7 +569,7 @@ test "cdp.lp: handleJavaScriptDialog controls confirm/prompt/alert return values const p_text_str = try p_text.toStringSlice(); try testing.expectEqualSlices(u8, "hello", p_text_str); - // ---- prompt: accept=true without promptText returns "" per CDP spec ---- + // ---- prompt: accept=true without promptText AND no dialog defaultText returns "" ---- try ctx.processMessage(.{ .id = 4, .method = "LP.handleJavaScriptDialog", .params = .{ .accept = true } }); try ctx.expectSentResult(null, .{ .id = 4 }); @@ -577,10 +577,35 @@ test "cdp.lp: handleJavaScriptDialog controls confirm/prompt/alert return values const p_empty_str = try p_empty.toStringSlice(); try testing.expectEqualSlices(u8, "", p_empty_str); - // ---- prompt: accept=false makes prompt() return null ---- - try ctx.processMessage(.{ .id = 5, .method = "LP.handleJavaScriptDialog", .params = .{ .accept = false } }); + // ---- prompt: accept=true without promptText falls back to dialog defaultText ---- + // Mirrors Chrome's accept-without-typing behavior: with no client-supplied + // promptText, the prompt's return value is the second arg to window.prompt. + try ctx.processMessage(.{ .id = 5, .method = "LP.handleJavaScriptDialog", .params = .{ .accept = true } }); try ctx.expectSentResult(null, .{ .id = 5 }); + const p_default_text = try ls.local.exec("prompt('name?', 'John Smith')", null); + const p_default_text_str = try p_default_text.toStringSlice(); + try testing.expectEqualSlices(u8, "John Smith", p_default_text_str); + + // ---- prompt: pre-armed promptText overrides the dialog defaultText ---- + try ctx.processMessage(.{ .id = 6, .method = "LP.handleJavaScriptDialog", .params = .{ .accept = true, .promptText = "typed" } }); + try ctx.expectSentResult(null, .{ .id = 6 }); + + const p_override = try ls.local.exec("prompt('name?', 'John Smith')", null); + const p_override_str = try p_override.toStringSlice(); + try testing.expectEqualSlices(u8, "typed", p_override_str); + + // ---- prompt: accept=false returns null regardless of dialog defaultText ---- + try ctx.processMessage(.{ .id = 7, .method = "LP.handleJavaScriptDialog", .params = .{ .accept = false } }); + try ctx.expectSentResult(null, .{ .id = 7 }); + + const p_dismiss_with_default = try ls.local.exec("prompt('cancel?', 'John Smith')", null); + try testing.expect(p_dismiss_with_default.isNull()); + + // ---- prompt: accept=false makes prompt() return null ---- + try ctx.processMessage(.{ .id = 8, .method = "LP.handleJavaScriptDialog", .params = .{ .accept = false } }); + try ctx.expectSentResult(null, .{ .id = 8 }); + const p_dismiss = try ls.local.exec("prompt('cancel?')", null); try testing.expect(p_dismiss.isNull()); @@ -589,8 +614,8 @@ test "cdp.lp: handleJavaScriptDialog controls confirm/prompt/alert return values try testing.expect(p_default.isNull()); // ---- alert: dispatches the event but has no return value to override ---- - try ctx.processMessage(.{ .id = 6, .method = "LP.handleJavaScriptDialog", .params = .{ .accept = true } }); - try ctx.expectSentResult(null, .{ .id = 6 }); + try ctx.processMessage(.{ .id = 9, .method = "LP.handleJavaScriptDialog", .params = .{ .accept = true } }); + try ctx.expectSentResult(null, .{ .id = 9 }); _ = try ls.local.exec("alert('important')", null); try ctx.expectSentEvent("Page.javascriptDialogOpening", .{ .message = "important",