diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.cpp b/Libraries/LibWeb/HTML/HTMLButtonElement.cpp
index 1e4b836dd54..4ea99239cf5 100644
--- a/Libraries/LibWeb/HTML/HTMLButtonElement.cpp
+++ b/Libraries/LibWeb/HTML/HTMLButtonElement.cpp
@@ -173,4 +173,50 @@ bool HTMLButtonElement::is_focusable() const
return enabled();
}
+// https://html.spec.whatwg.org/multipage/form-elements.html#dom-button-command
+String HTMLButtonElement::command() const
+{
+ // 1. Let command be this's command attribute.
+ auto command = get_attribute(AttributeNames::command);
+
+ // https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-command
+ // The command attribute is an enumerated attribute with the following keywords and states:
+ // Keyword State Brief description
+ // toggle-popover Toggle Popover Shows or hides the targeted popover element.
+ // show-popover Show Popover Shows the targeted popover element.
+ // hide-popover Hide Popover Hides the targeted popover element.
+ // close Close Closes the targeted dialog element.
+ // show-modal Show Modal Opens the targeted dialog element as modal.
+ // A custom command keyword Custom Only dispatches the command event on the targeted element.
+ Array valid_values { "toggle-popover"_string, "show-popover"_string, "hide-popover"_string, "close"_string, "show-modal"_string };
+
+ // 2. If command is in the Custom state, then return command's value.
+ // A custom command keyword is a string that starts with "--".
+ if (command.has_value() && command.value().starts_with_bytes("--"sv)) {
+ return command.value();
+ }
+
+ // NOTE: Steps are re-ordered a bit.
+
+ // 4. Return the keword corresponding to the value of command.return
+ if (command.has_value()) {
+ auto command_value = command.value();
+ for (auto const& value : valid_values) {
+ if (value.equals_ignoring_ascii_case(command_value)) {
+ return value;
+ }
+ }
+ }
+
+ // 3. If command is in the Unknown state, then return the empty string.
+ // The attribute's missing value default and invalid value default are both the Unknown state.
+ return {};
+}
+
+// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:dom-button-command-2
+WebIDL::ExceptionOr HTMLButtonElement::set_command(String const& value)
+{
+ return set_attribute(AttributeNames::command, value);
+}
+
}
diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.h b/Libraries/LibWeb/HTML/HTMLButtonElement.h
index 831256ac816..d30d0fe3d52 100644
--- a/Libraries/LibWeb/HTML/HTMLButtonElement.h
+++ b/Libraries/LibWeb/HTML/HTMLButtonElement.h
@@ -80,6 +80,9 @@ public:
virtual bool has_activation_behavior() const override;
virtual void activation_behavior(DOM::Event const&) override;
+ String command() const;
+ WebIDL::ExceptionOr set_command(String const& command);
+
private:
virtual void visit_edges(Visitor&) override;
diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.idl b/Libraries/LibWeb/HTML/HTMLButtonElement.idl
index 7a691f0733f..9746bae0635 100644
--- a/Libraries/LibWeb/HTML/HTMLButtonElement.idl
+++ b/Libraries/LibWeb/HTML/HTMLButtonElement.idl
@@ -15,6 +15,7 @@ enum ButtonTypeState {
interface HTMLButtonElement : HTMLElement {
[HTMLConstructor] constructor();
+ [CEReactions] attribute DOMString command;
[CEReactions, Reflect] attribute boolean disabled;
readonly attribute HTMLFormElement? form;
[CEReactions] attribute USVString formAction;
diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/command-reflection.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/command-reflection.txt
new file mode 100644
index 00000000000..7cb8811fffa
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/command-reflection.txt
@@ -0,0 +1,21 @@
+Harness status: OK
+
+Found 16 tests
+
+16 Pass
+Pass invoker should reflect properly
+Pass invoker should reflect show-modal properly
+Pass invoker should reflect toggle-popover properly
+Pass invoker should reflect hide-popover properly
+Pass invoker should reflect show-popover properly
+Pass invoker should reflect close properly
+Pass invoker should reflect --custom properly
+Pass invoker should reflect odd cased sHoW-MoDaL properly - as show-modal
+Pass invoker should reflect odd cased tOgGlE-pOpOvEr properly - as toggle-popover
+Pass invoker should reflect odd cased hIdE-pOpOvEr properly - as hide-popover
+Pass invoker should reflect odd cased sHoW-pOpOvEr properly - as show-popover
+Pass invoker should reflect odd cased ClOsE properly - as close
+Pass invoker should reflect odd cased --cUsToM properly - as --cUsToM
+Pass invoker should reflect the invalid value "invalid" as the empty string
+Pass invoker should reflect the invalid value "show-invalid" as the empty string
+Pass invoker should reflect the invalid value "foo-bar" as the empty string
\ No newline at end of file
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/command-reflection.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/command-reflection.html
new file mode 100644
index 00000000000..e6e6f0e6145
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/command-reflection.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/resources/invoker-utils.js b/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/resources/invoker-utils.js
new file mode 100644
index 00000000000..4261f9c0d32
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/resources/invoker-utils.js
@@ -0,0 +1,27 @@
+function waitForRender() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+}
+async function clickOn(element) {
+ await waitForRender();
+ let rect = element.getBoundingClientRect();
+ let actions = new test_driver.Actions();
+ // FIXME: Switch to pointerMove(0, 0, {origin: element}) once
+ // https://github.com/web-platform-tests/wpt/issues/41257 is fixed.
+ await actions
+ .pointerMove(Math.round(rect.x + rect.width / 2), Math.round(rect.y + rect.height / 2), {})
+ .pointerDown({button: actions.ButtonType.LEFT})
+ .pointerUp({button: actions.ButtonType.LEFT})
+ .send();
+ await waitForRender();
+}
+async function hoverOver(element) {
+ await waitForRender();
+ let rect = element.getBoundingClientRect();
+ let actions = new test_driver.Actions();
+ // FIXME: Switch to pointerMove(0, 0, {origin: element}) once
+ // https://github.com/web-platform-tests/wpt/issues/41257 is fixed.
+ await actions
+ .pointerMove(Math.round(rect.x + rect.width / 2), Math.round(rect.y + rect.height / 2), {})
+ .send();
+ await waitForRender();
+}