From b405bed6607c24e44b92fae813325efeb9167366 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 4 Jun 2026 12:47:12 +0200 Subject: [PATCH] Validate form constraints on interactive submit; add HTMLFormElement.noValidate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now Frame.submitForm performed no constraint validation: requestSubmit() and interactive submits (Enter, clicking a submit button) fired the `submit` event and proceeded regardless of control validity — a `
` would submit with the field empty. Implement the HTML "submit a form element" interactive-validation step: in the fire_event branch, run form.checkValidity() as a gate before dispatching `submit`, abort submission when it fails (checkValidity fires `invalid` on the offending controls), and skip the gate when the form is in the no-validate state. form.submit() keeps bypassing validation, per spec. To drive the no-validate state, add the HTMLFormElement.noValidate IDL attribute reflecting the `novalidate` content attribute, and honor a submitter's `formnovalidate`. noValidate gates submission only — checkValidity()/ reportValidity() still validate unconditionally. --- src/browser/Frame.zig | 15 ++++++++ .../tests/element/html/form-validity.html | 18 +++++++++ src/browser/tests/element/html/form.html | 38 +++++++++++++++++++ src/browser/webapi/element/html/Form.zig | 13 +++++++ 4 files changed, 84 insertions(+) diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index 095999c0..dd120a86 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -4093,6 +4093,21 @@ pub fn submitForm(self: *Frame, submitter_: ?*Element, form_: ?*Element.Html.For form._firing_submission_events = true; defer form._firing_submission_events = false; + // Per the HTML "submit a form element" algorithm: unless the form (or the + // submitter, via formnovalidate) is in the no-validate state, interactively + // validate the form's constraints and abort submission if it fails. + // checkValidity() fires the `invalid` events on the offending controls. + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-submit + const skip_validation = form.getNoValidate() or blk: { + const s = submit_button orelse break :blk false; + if (s.is(Element.Html.Form.Input)) |input| break :blk input.getFormNoValidate(); + if (s.is(Element.Html.Form.Button)) |button| break :blk button.getFormNoValidate(); + break :blk false; + }; + if (!skip_validation and !try form.checkValidity(self)) { + return; + } + // Per HTML spec "submit a form element" algorithm: SubmitEvent.submitter // must be null when the submitter is the form itself, which is what // Form.requestSubmit() passes when called with no submitter argument. diff --git a/src/browser/tests/element/html/form-validity.html b/src/browser/tests/element/html/form-validity.html index becce81d..1ce1b305 100644 --- a/src/browser/tests/element/html/form-validity.html +++ b/src/browser/tests/element/html/form-validity.html @@ -13,6 +13,8 @@
+
+ + + + +
+ + + +
+ + +
diff --git a/src/browser/webapi/element/html/Form.zig b/src/browser/webapi/element/html/Form.zig index 1db68e3b..bd634b6f 100644 --- a/src/browser/webapi/element/html/Form.zig +++ b/src/browser/webapi/element/html/Form.zig @@ -142,6 +142,18 @@ pub fn setAcceptCharset(self: *Form, value: []const u8, frame: *Frame) !void { try self.asElement().setAttributeSafe(.wrap("accept-charset"), .wrap(value), frame); } +pub fn getNoValidate(self: *const Form) bool { + return self.asConstElement().getAttributeSafe(comptime .wrap("novalidate")) != null; +} + +pub fn setNoValidate(self: *Form, value: bool, frame: *Frame) !void { + if (value) { + try self.asElement().setAttributeSafe(comptime .wrap("novalidate"), .wrap(""), frame); + } else { + try self.asElement().removeAttribute(comptime .wrap("novalidate"), frame); + } +} + pub fn getEnctype(self: *const Form) []const u8 { return normalizeEnctype(self.asConstElement().getAttributeSafe(comptime .wrap("enctype")), "application/x-www-form-urlencoded"); } @@ -241,6 +253,7 @@ pub const JsApi = struct { pub const target = bridge.accessor(Form.getTarget, Form.setTarget, .{ .ce_reactions = true }); pub const acceptCharset = bridge.accessor(Form.getAcceptCharset, Form.setAcceptCharset, .{ .ce_reactions = true }); pub const enctype = bridge.accessor(Form.getEnctype, Form.setEnctype, .{ .ce_reactions = true }); + pub const noValidate = bridge.accessor(Form.getNoValidate, Form.setNoValidate, .{ .ce_reactions = true }); pub const elements = bridge.accessor(Form.getElements, null, .{}); pub const length = bridge.accessor(Form.getLength, null, .{}); pub const submit = bridge.function(Form.submit, .{});