diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index 068587b0..28e0ef9a 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -4139,6 +4139,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, .{});