diff --git a/Tests/LibWeb/Text/expected/HTML/formAction-attribute.txt b/Tests/LibWeb/Text/expected/HTML/formAction-attribute.txt
new file mode 100644
index 00000000000..aa202b2f6c8
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/HTML/formAction-attribute.txt
@@ -0,0 +1,3 @@
+button.formAction initial value: http://www.example.com/
+Final segment of button.formAction after setting to the empty string: formAction-attribute.html
+Final segment of button.formAction after setting to "../test.html": test.html
diff --git a/Tests/LibWeb/Text/input/HTML/formAction-attribute.html b/Tests/LibWeb/Text/input/HTML/formAction-attribute.html
new file mode 100644
index 00000000000..9cdddbf95b9
--- /dev/null
+++ b/Tests/LibWeb/Text/input/HTML/formAction-attribute.html
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp
index 41ba8929b59..7cbe3e6e3d8 100644
--- a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp
@@ -166,6 +166,27 @@ void FormAssociatedElement::reset_form_owner()
}
}
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-fs-formaction
+String FormAssociatedElement::form_action() const
+{
+ // The formAction IDL attribute must reflect the formaction content attribute, except that on getting, when the content attribute is missing or its value is the empty string,
+ // the element's node document's URL must be returned instead.
+ auto& html_element = form_associated_element_to_html_element();
+ auto form_action_attribute = html_element.attribute(HTML::AttributeNames::formaction);
+ if (!form_action_attribute.has_value() || form_action_attribute.value().is_empty()) {
+ return html_element.document().url_string();
+ }
+
+ auto document_base_url = html_element.document().base_url();
+ return MUST(document_base_url.complete_url(form_action_attribute.value()).to_string());
+}
+
+WebIDL::ExceptionOr FormAssociatedElement::set_form_action(String const& value)
+{
+ auto& html_element = form_associated_element_to_html_element();
+ return html_element.set_attribute(HTML::AttributeNames::formaction, value);
+}
+
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
void FormAssociatedTextControlElement::relevant_value_was_changed(JS::GCPtr text_node)
{
diff --git a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h
index d1037faca2d..a43273efeb0 100644
--- a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h
+++ b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h
@@ -98,6 +98,9 @@ public:
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-reset-control
virtual void reset_algorithm() {};
+ String form_action() const;
+ WebIDL::ExceptionOr set_form_action(String const&);
+
protected:
FormAssociatedElement() = default;
virtual ~FormAssociatedElement() = default;
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl
index 47f6769d78d..f373c070526 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl
@@ -15,7 +15,7 @@ interface HTMLButtonElement : HTMLElement {
[CEReactions, Reflect] attribute boolean disabled;
readonly attribute HTMLFormElement? form;
- [CEReactions, Reflect=formaction] attribute USVString formAction;
+ [CEReactions] attribute USVString formAction;
[CEReactions, Reflect=formenctype] attribute DOMString formEnctype;
[CEReactions, Reflect=formmethod] attribute DOMString formMethod;
[CEReactions, Reflect=formnovalidate] attribute boolean formNoValidate;