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',
+ `` +
+ ``);
+ 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);
+}