From cf14b9e762c099de47e636eacaa68269247c70e3 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 29 Dec 2025 10:35:05 +0100 Subject: [PATCH 1/6] add Document.hasFocus placeholder --- src/browser/webapi/Document.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index d0490555..32024bb2 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -594,6 +594,11 @@ pub fn getAdoptedStyleSheets(self: *Document, page: *Page) !js.Object { return obj; } +pub fn hasFocus(_: *Document) bool { + log.debug(.not_implemented, "Document.hasFocus", .{}); + return true; +} + pub fn setAdoptedStyleSheets(self: *Document, sheets: js.Object) !void { self._adopted_style_sheets = try sheets.persist(); } @@ -675,6 +680,7 @@ pub const JsApi = struct { return page.window; } }.defaultView, null, .{ .cache = "defaultView" }); + pub const hasFocus = bridge.function(Document.hasFocus, .{}); }; const testing = @import("../../testing.zig"); From d697944b5ad1bddb429a4862266fc84cf6a0b0fe Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 29 Dec 2025 10:35:26 +0100 Subject: [PATCH 2/6] add Input.select() --- src/browser/webapi/element/html/Input.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/browser/webapi/element/html/Input.zig b/src/browser/webapi/element/html/Input.zig index 865912d2..437cae8a 100644 --- a/src/browser/webapi/element/html/Input.zig +++ b/src/browser/webapi/element/html/Input.zig @@ -75,6 +75,7 @@ _value: ?[]const u8 = null, _checked: bool = false, _checked_dirty: bool = false, _input_type: Type = .text, +_selected: bool = false, pub fn asElement(self: *Input) *Element { return self._proto._proto; @@ -246,6 +247,10 @@ pub fn setRequired(self: *Input, required: bool, page: *Page) !void { } } +pub fn select(self: *Input) void { + self._selected = true; +} + pub fn getForm(self: *Input, page: *Page) ?*Form { const element = self.asElement(); @@ -337,6 +342,7 @@ pub const JsApi = struct { pub const size = bridge.accessor(Input.getSize, Input.setSize, .{}); pub const src = bridge.accessor(Input.getSrc, Input.setSrc, .{}); pub const form = bridge.accessor(Input.getForm, null, .{}); + pub const select = bridge.function(Input.select, .{}); }; pub const Build = struct { From 3e52abf47146ce2914d848555b14b9d46fc8389b Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 29 Dec 2025 10:53:41 +0100 Subject: [PATCH 3/6] cdp: add input.insertText --- src/browser/Page.zig | 31 +++++++++++++++++++++++++++++++ src/cdp/domains/input.zig | 16 ++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 37f92b57..cbda9900 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -2571,6 +2571,37 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form return self.scheduleNavigation(action, opts, .form); } +// insertText is a shortcut to insert text into the active element. +pub fn insertText(self: *Page, v: []const u8) !void { + const html_element = self.document._active_element orelse return; + + if (html_element.is(Element.Html.Input)) |input| { + const input_type = input._input_type; + if (input_type == .radio or input_type == .checkbox) { + return; + } + + // If the input is selected, replace the existing value + if (input._selected) { + const new_value = try self.arena.dupe(u8, v); + try input.setValue(new_value, self); + input._selected = false; + return; + } + + // Or append the value + const current_value = input.getValue(); + const new_value = try std.mem.concat(self.arena, u8, &.{ current_value, v }); + try input.setValue(new_value, self); + } + + if (html_element.is(Element.Html.TextArea)) |textarea| { + const current_value = textarea.getValue(); + const new_value = try std.mem.concat(self.arena, u8, &.{ current_value, v }); + try textarea.setValue(new_value, self); + } +} + const RequestCookieOpts = struct { is_http: bool = true, is_navigation: bool = false, diff --git a/src/cdp/domains/input.zig b/src/cdp/domains/input.zig index 777f7049..f97ee123 100644 --- a/src/cdp/domains/input.zig +++ b/src/cdp/domains/input.zig @@ -22,11 +22,13 @@ pub fn processMessage(cmd: anytype) !void { const action = std.meta.stringToEnum(enum { dispatchKeyEvent, dispatchMouseEvent, + insertText, }, cmd.input.action) orelse return error.UnknownMethod; switch (action) { .dispatchKeyEvent => return dispatchKeyEvent(cmd), .dispatchMouseEvent => return dispatchMouseEvent(cmd), + .insertText => return insertText(cmd), } } @@ -101,6 +103,20 @@ fn dispatchMouseEvent(cmd: anytype) !void { // result already sent } +// https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-insertText +fn insertText(cmd: anytype) !void { + const params = (try cmd.params(struct { + text: []const u8, // The text to insert + })) orelse return error.InvalidParams; + + const bc = cmd.browser_context orelse return; + const page = bc.session.currentPage() orelse return; + + try page.insertText(params.text); + + try cmd.sendResult(null, .{}); +} + fn clickNavigate(cmd: anytype, uri: std.Uri) !void { const bc = cmd.browser_context.?; From 2d6c37fa6f1627755a8730c9de589a3505cb9b18 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 29 Dec 2025 11:39:57 +0100 Subject: [PATCH 4/6] handle input selection when keydown --- src/browser/Page.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index cbda9900..fed0ba00 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -2510,6 +2510,13 @@ pub fn handleKeydown(self: *Page, target: *Node, event: *Event) !void { // Handle printable characters if (key.isPrintable()) { + // if the input is selected, replace the content. + if (input._selected) { + const new_value = try self.arena.dupe(u8, key.asString()); + try input.setValue(new_value, self); + input._selected = false; + return; + } const current_value = input.getValue(); const new_value = try std.mem.concat(self.arena, u8, &.{ current_value, key.asString() }); try input.setValue(new_value, self); From 76f30dc98591c4ae08ab64ca8ff3a589e8e4e87c Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 29 Dec 2025 11:11:41 +0100 Subject: [PATCH 5/6] zig fmt --- src/log.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/log.zig b/src/log.zig index 0f25809b..f6cce6b1 100644 --- a/src/log.zig +++ b/src/log.zig @@ -159,7 +159,7 @@ fn logLogfmt(comptime scope: Scope, level: Level, comptime msg: []const u8, data inline for (@typeInfo(@TypeOf(data)).@"struct".fields) |f| { const value = @field(data, f.name); if (std.meta.hasMethod(@TypeOf(value), "logFmt")) { - try value.logFmt(f.name, LogFormatWriter{.writer = writer}); + try value.logFmt(f.name, LogFormatWriter{ .writer = writer }); } else { const key = " " ++ f.name ++ "="; try writer.writeAll(key); From 27f6f4243f375088ab293eb56625d92beb5c4ada Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 29 Dec 2025 12:08:07 +0100 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Karl Seguin --- src/browser/Page.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index fed0ba00..d5c9e2f5 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -2599,13 +2599,13 @@ pub fn insertText(self: *Page, v: []const u8) !void { // Or append the value const current_value = input.getValue(); const new_value = try std.mem.concat(self.arena, u8, &.{ current_value, v }); - try input.setValue(new_value, self); + return input.setValue(new_value, self); } if (html_element.is(Element.Html.TextArea)) |textarea| { const current_value = textarea.getValue(); const new_value = try std.mem.concat(self.arena, u8, &.{ current_value, v }); - try textarea.setValue(new_value, self); + return textarea.setValue(new_value, self); } }