diff --git a/Tests/LibWeb/Text/expected/DOM/Element-removeAttributeNode.txt b/Tests/LibWeb/Text/expected/DOM/Element-removeAttributeNode.txt
new file mode 100644
index 00000000000..2227e6985e8
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/DOM/Element-removeAttributeNode.txt
@@ -0,0 +1,2 @@
+true
+OK: NotFoundError: Attribute not found
diff --git a/Tests/LibWeb/Text/input/DOM/Element-removeAttributeNode.html b/Tests/LibWeb/Text/input/DOM/Element-removeAttributeNode.html
new file mode 100644
index 00000000000..56a2904f21b
--- /dev/null
+++ b/Tests/LibWeb/Text/input/DOM/Element-removeAttributeNode.html
@@ -0,0 +1,16 @@
+
+
diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp
index 3ac6ad6f6ba..33125182bb7 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Element.cpp
@@ -305,6 +305,12 @@ void Element::remove_attribute_ns(Optional const& namespace_, FlyStri
m_attributes->remove_attribute_ns(namespace_, name);
}
+// https://dom.spec.whatwg.org/#dom-element-removeattributenode
+WebIDL::ExceptionOr> Element::remove_attribute_node(JS::NonnullGCPtr attr)
+{
+ return m_attributes->remove_attribute_node(attr);
+}
+
// https://dom.spec.whatwg.org/#dom-element-hasattribute
bool Element::has_attribute(FlyString const& name) const
{
diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h
index 8ccc83c0742..6f4d33db7b7 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.h
+++ b/Userland/Libraries/LibWeb/DOM/Element.h
@@ -112,6 +112,7 @@ public:
void append_attribute(Attr&);
void remove_attribute(FlyString const& name);
void remove_attribute_ns(Optional const& namespace_, FlyString const& name);
+ WebIDL::ExceptionOr> remove_attribute_node(JS::NonnullGCPtr);
WebIDL::ExceptionOr toggle_attribute(FlyString const& name, Optional force);
size_t attribute_list_size() const;
diff --git a/Userland/Libraries/LibWeb/DOM/Element.idl b/Userland/Libraries/LibWeb/DOM/Element.idl
index 99710c3f4c6..e5180dadb26 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.idl
+++ b/Userland/Libraries/LibWeb/DOM/Element.idl
@@ -51,7 +51,7 @@ interface Element : Node {
Attr? getAttributeNodeNS([FlyString] DOMString? namespace, [FlyString] DOMString localName);
[CEReactions] Attr? setAttributeNode(Attr attr);
[CEReactions] Attr? setAttributeNodeNS(Attr attr);
- // FIXME: [CEReactions] Attr removeAttributeNode(Attr attr);
+ [CEReactions] Attr removeAttributeNode(Attr attr);
ShadowRoot attachShadow(ShadowRootInit init);
readonly attribute ShadowRoot? shadowRoot;
diff --git a/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp
index e95f2c967d1..e60a4c7b3b0 100644
--- a/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp
+++ b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.cpp
@@ -342,4 +342,19 @@ WebIDL::ExceptionOr NamedNodeMap::named_item_value(FlyString const& n
return node;
}
+// https://dom.spec.whatwg.org/#dom-element-removeattributenode
+WebIDL::ExceptionOr> NamedNodeMap::remove_attribute_node(JS::NonnullGCPtr attr)
+{
+ // 1. If this’s attribute list does not contain attr, then throw a "NotFoundError" DOMException.
+ auto index = m_attributes.find_first_index(attr);
+ if (!index.has_value())
+ return WebIDL::NotFoundError::create(realm(), "Attribute not found"_fly_string);
+
+ // 2. Remove attr.
+ remove_attribute_at_index(index.value());
+
+ // 3. Return attr.
+ return attr;
+}
+
}
diff --git a/Userland/Libraries/LibWeb/DOM/NamedNodeMap.h b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.h
index 34949c1e404..55760db5093 100644
--- a/Userland/Libraries/LibWeb/DOM/NamedNodeMap.h
+++ b/Userland/Libraries/LibWeb/DOM/NamedNodeMap.h
@@ -56,6 +56,8 @@ public:
Attr const* get_attribute_with_lowercase_qualified_name(FlyString const&) const;
+ WebIDL::ExceptionOr> remove_attribute_node(JS::NonnullGCPtr);
+
private:
explicit NamedNodeMap(Element&);