mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
Merge pull request #2639 from lightpanda-io/form-no-validate
Validate form constraints on interactive submit; add HTMLFormElement.…
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
<form id="empty-form"></form>
|
||||
|
||||
<form id="novalidate-form" novalidate></form>
|
||||
|
||||
<script id="surface">
|
||||
{
|
||||
const f = $('#valid-form');
|
||||
@@ -46,6 +48,22 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="no_validate_reflects_attribute">
|
||||
{
|
||||
// novalidate content attribute reflects to the noValidate IDL boolean
|
||||
testing.expectEqual(true, $('#novalidate-form').noValidate);
|
||||
testing.expectEqual(false, $('#valid-form').noValidate);
|
||||
|
||||
const f = $('#empty-form');
|
||||
f.noValidate = true;
|
||||
testing.expectEqual(true, f.noValidate);
|
||||
testing.expectEqual('', f.getAttribute('novalidate'));
|
||||
f.noValidate = false;
|
||||
testing.expectEqual(false, f.noValidate);
|
||||
testing.expectEqual(null, f.getAttribute('novalidate'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="report_validity_matches">
|
||||
{
|
||||
testing.expectEqual(false, $('#invalid-form').reportValidity());
|
||||
|
||||
@@ -490,6 +490,44 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Test: interactive submission validates constraints unless no-validate is set -->
|
||||
<form id="test_form_validate" action="/should-not-navigate-v" method="get">
|
||||
<input type="text" name="q" required>
|
||||
<button id="v_submit" type="submit">Go</button>
|
||||
<button id="v_submit_novalidate" type="submit" formnovalidate>Go</button>
|
||||
</form>
|
||||
|
||||
<script id="requestSubmit_validates_constraints">
|
||||
{
|
||||
const form = $('#test_form_validate');
|
||||
const field = form.querySelector('input[name=q]');
|
||||
let submitFired = 0;
|
||||
let invalidFired = 0;
|
||||
form.addEventListener('submit', (e) => { e.preventDefault(); submitFired++; });
|
||||
field.addEventListener('invalid', () => { invalidFired++; });
|
||||
|
||||
// Required field is empty: submission is blocked and `invalid` fires.
|
||||
form.requestSubmit($('#v_submit'));
|
||||
testing.expectEqual(0, submitFired);
|
||||
testing.expectEqual(1, invalidFired);
|
||||
|
||||
// A submitter with formnovalidate bypasses validation.
|
||||
form.requestSubmit($('#v_submit_novalidate'));
|
||||
testing.expectEqual(1, submitFired);
|
||||
|
||||
// form.noValidate also bypasses validation.
|
||||
form.noValidate = true;
|
||||
form.requestSubmit($('#v_submit'));
|
||||
testing.expectEqual(2, submitFired);
|
||||
|
||||
// Once valid, validation passes and submission proceeds.
|
||||
form.noValidate = false;
|
||||
field.value = 'ok';
|
||||
form.requestSubmit($('#v_submit'));
|
||||
testing.expectEqual(3, submitFired);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Test: requestSubmit() with submitter not owned by form throws NotFoundError -->
|
||||
<form id="test_form_rs2" action="/should-not-navigate5" method="get">
|
||||
<input type="text" name="q" value="test">
|
||||
|
||||
@@ -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, .{});
|
||||
|
||||
Reference in New Issue
Block a user