diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp index 508267b964e..e08195303cc 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp @@ -350,6 +350,27 @@ bool FormAssociatedElement::satisfies_its_constraints() const suffering_from_being_missing() || suffering_from_a_type_mismatch() || suffering_from_a_pattern_mismatch() || suffering_from_being_too_long() || suffering_from_being_too_short() || suffering_from_an_underflow() || suffering_from_an_overflow() || suffering_from_a_step_mismatch() || suffering_from_bad_input() || suffering_from_a_custom_error()); } +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fs-novalidate +bool FormAssociatedElement::novalidate_state() const +{ + // The no-validate state of an element is true if the element is a submit button ... + if (!is_submit_button()) + return false; + + // ..., and the element's formnovalidate attribute is present, ... + auto const& html_element = form_associated_element_to_html_element(); + if (html_element.has_attribute(HTML::AttributeNames::formnovalidate)) + return true; + + // ... or if the element's form owner's novalidate attribute is present, ... + auto* form = this->form(); + if (form && form->has_attribute(HTML::AttributeNames::novalidate)) + return true; + + // ... and false otherwise. + return false; +} + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#limiting-user-input-length%3A-the-maxlength-attribute%3Asuffering-from-being-too-long bool FormAssociatedElement::suffering_from_being_too_long() const { diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Libraries/LibWeb/HTML/FormAssociatedElement.h index 6941e64ecce..dc7d372c74e 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.h +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.h @@ -110,6 +110,9 @@ public: // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fv-valid bool satisfies_its_constraints() const; + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fs-novalidate + bool novalidate_state() const; + // https://html.spec.whatwg.org/multipage/form-control-infrastructure/#definitions virtual bool suffering_from_being_missing() const { return false; } virtual bool suffering_from_a_type_mismatch() const { return false; } diff --git a/Libraries/LibWeb/HTML/HTMLFormElement.cpp b/Libraries/LibWeb/HTML/HTMLFormElement.cpp index 4873aa5365b..7eff12ad86f 100644 --- a/Libraries/LibWeb/HTML/HTMLFormElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLFormElement.cpp @@ -139,11 +139,20 @@ WebIDL::ExceptionOr HTMLFormElement::submit_form(GC::Ref subm } } - // FIXME: 4. If the submitter element's no-validate state is false, then interactively validate the constraints - // of form and examine the result. If the result is negative (i.e., the constraint validation concluded - // that there were invalid fields and probably informed the user of this), then: - // 1. Set form's firing submission events to false. - // 2. Return. + // 4. If the submitter element's no-validate state is false, then interactively validate the constraints + // of form and examine the result. If the result is negative (i.e., the constraint validation concluded + // that there were invalid fields and probably informed the user of this), then: + auto* form_associated_element = as_if(*submitter); + if (form_associated_element && !form_associated_element->novalidate_state()) { + auto validation_result = interactively_validate_constraints(); + if (!validation_result) { + // 1. Set form's firing submission events to false. + m_firing_submission_events = false; + + // 2. Return. + return {}; + } + } // 5. Let submitterButton be null if submitter is form. Otherwise, let submitterButton be submitter. GC::Ptr submitter_button; diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/form-submission-0/historical.window.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/form-submission-0/historical.window.txt new file mode 100644 index 00000000000..cbeea10b27a --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/form-submission-0/historical.window.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass invalid event is only supported for form controls \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/form-submission-0/historical.window.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/form-submission-0/historical.window.html new file mode 100644 index 00000000000..d8ba2d8131c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/form-submission-0/historical.window.html @@ -0,0 +1,8 @@ + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/form-submission-0/historical.window.js b/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/form-submission-0/historical.window.js new file mode 100644 index 00000000000..fcc47d90f66 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/form-submission-0/historical.window.js @@ -0,0 +1,19 @@ +// META: script=./resources/targetted-form.js + +test(t => { + const form = populateForm(''); + t.add_cleanup(() => { + form.previousElementSibling.remove(); + form.remove(); + }); + const submitter = form.querySelector('input[type=submit]'); + let invalid = form.querySelector('[required]'); + let targets = []; + const listener = e => targets.push(e.target.localName); + form.addEventListener("invalid", t.step_func(listener)); + form.oninvalid = t.step_func(listener); + invalid.addEventListener("invalid", t.step_func(listener)); + invalid.oninvalid = t.step_func(listener); + submitter.click(); + assert_array_equals(targets, ["input", "input"]); +}, "invalid event is only supported for form controls"); diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/form-submission-0/resources/targetted-form.js b/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/form-submission-0/resources/targetted-form.js new file mode 100644 index 00000000000..52482c859f4 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/form-submission-0/resources/targetted-form.js @@ -0,0 +1,38 @@ +let frameCounter = 0; + +function populateForm(optionalContentHtml) { + if (!optionalContentHtml) + optionalContentHtml = ''; + const frameName = "form-test-target-" + frameCounter++; + document.body.insertAdjacentHTML( + 'afterbegin', + `` + + `
${optionalContentHtml}
`); + return document.getElementsByName(frameName)[0].nextSibling; +} + +function submitPromise(form, iframe) { + return new Promise((resolve, reject) => { + iframe.onload = () => resolve(iframe.contentWindow.location.search); + iframe.onerror = () => reject(new Error('iframe onerror fired')); + form.submit(); + }); +} + +function loadPromise(iframe) { + return new Promise((resolve, reject) => { + iframe.onload = function() { + // The initial about:blank load event can be fired before the form navigation occurs. + // See https://github.com/whatwg/html/issues/490 for more information. + if (iframe.contentWindow.location == "about:blank") { return; } + resolve(); + }; + iframe.onerror = () => reject(new Error('iframe onerror fired')); + }); +} + +function getParamValue(iframe, paramName) { + let params = (new URL(iframe.contentWindow.location)).searchParams; + return params.get(paramName); +}