diff --git a/Tests/LibWeb/Text/expected/HTML/HTMLElement-labels.txt b/Tests/LibWeb/Text/expected/HTML/HTMLElement-labels.txt
new file mode 100644
index 00000000000..8138db450b5
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/HTML/HTMLElement-labels.txt
@@ -0,0 +1,21 @@
+input.labels.length: 1
+input.labels[0] === label: true
+input.labels always returns the same object: true
+meter.labels.length: 1
+meter.labels[0] === label: true
+meter.labels always returns the same object: true
+output.labels.length: 1
+output.labels[0] === label: true
+output.labels always returns the same object: true
+progress.labels.length: 1
+progress.labels[0] === label: true
+progress.labels always returns the same object: true
+select.labels.length: 1
+select.labels[0] === label: true
+select.labels always returns the same object: true
+textarea.labels.length: 1
+textarea.labels[0] === label: true
+textarea.labels always returns the same object: true
+input.labels returns null if input type is hidden: true
+input.labels.length after input type is changed from hidden: 1
+input.labels[0] === label after input type is changed from hidden: true
diff --git a/Tests/LibWeb/Text/input/HTML/HTMLElement-labels.html b/Tests/LibWeb/Text/input/HTML/HTMLElement-labels.html
new file mode 100644
index 00000000000..0c9ea9d69ae
--- /dev/null
+++ b/Tests/LibWeb/Text/input/HTML/HTMLElement-labels.html
@@ -0,0 +1,36 @@
+
+
+
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl
index 6a7abc6bbd6..e7b8dae094e 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.idl
@@ -31,6 +31,6 @@ interface HTMLButtonElement : HTMLElement {
// FIXME: boolean reportValidity();
// FIXME: undefined setCustomValidity(DOMString error);
- // FIXME: readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
};
// FIXME: HTMLButtonElement includes PopoverInvokerElement;
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp
index 781f1c6768f..5b50352bfe2 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -20,6 +21,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -436,6 +438,25 @@ bool HTMLElement::fire_a_synthetic_pointer_event(FlyString const& type, DOM::Ele
return target.dispatch_event(event);
}
+// https://html.spec.whatwg.org/multipage/forms.html#dom-lfe-labels-dev
+JS::GCPtr HTMLElement::labels()
+{
+ // Labelable elements and all input elements have a live NodeList object associated with them that represents the list of label elements, in tree order,
+ // whose labeled control is the element in question. The labels IDL attribute of labelable elements that are not form-associated custom elements,
+ // and the labels IDL attribute of input elements, on getting, must return that NodeList object, and that same value must always be returned,
+ // unless this element is an input element whose type attribute is in the Hidden state, in which case it must instead return null.
+ if (!is_labelable())
+ return {};
+
+ if (!m_labels) {
+ m_labels = DOM::LiveNodeList::create(realm(), root(), DOM::LiveNodeList::Scope::Descendants, [&](auto& node) {
+ return is(node) && verify_cast(node).control() == this;
+ });
+ }
+
+ return m_labels;
+}
+
// https://html.spec.whatwg.org/multipage/interaction.html#dom-click
void HTMLElement::click()
{
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.h b/Userland/Libraries/LibWeb/HTML/HTMLElement.h
index e68bd7717ff..0b047bf0b85 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.h
@@ -68,6 +68,8 @@ public:
// https://html.spec.whatwg.org/multipage/forms.html#category-label
virtual bool is_labelable() const { return false; }
+ JS::GCPtr labels();
+
virtual Optional default_role() const override;
String get_an_elements_target() const;
@@ -93,6 +95,8 @@ private:
JS::GCPtr m_dataset;
+ JS::GCPtr m_labels;
+
enum class ContentEditableState {
True,
False,
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl
index a41d19732f7..9c103c8c6cb 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl
@@ -54,7 +54,7 @@ interface HTMLInputElement : HTMLElement {
boolean reportValidity();
undefined setCustomValidity(DOMString error);
- // FIXME: readonly attribute NodeList? labels;
+ readonly attribute NodeList? labels;
undefined select();
// FIXME: attribute unsigned long? selectionStart;
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.idl
index c8ad0c262f8..0b95f310c29 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.idl
@@ -11,5 +11,5 @@ interface HTMLMeterElement : HTMLElement {
[CEReactions] attribute double low;
[CEReactions] attribute double high;
[CEReactions] attribute double optimum;
- // FIXME: readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.idl
index 7bb6cba0107..00f8a4efed0 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLOutputElement.idl
@@ -21,5 +21,5 @@ interface HTMLOutputElement : HTMLElement {
// FIXME: boolean reportValidity();
// FIXME: undefined setCustomValidity(DOMString error);
- // FIXME: readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.idl
index a2d483f0c8a..07b5ca018ef 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLProgressElement.idl
@@ -8,5 +8,5 @@ interface HTMLProgressElement : HTMLElement {
[CEReactions] attribute double value;
[CEReactions] attribute double max;
readonly attribute double position;
- // FIXME: readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.idl
index 35b06dcd91a..92cde398156 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLSelectElement.idl
@@ -37,5 +37,5 @@ interface HTMLSelectElement : HTMLElement {
// FIXME: boolean reportValidity();
// FIXME: undefined setCustomValidity(DOMString error);
- // FIXME: readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
};
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl
index 52a2276f71f..be322031bfd 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl
@@ -32,7 +32,7 @@ interface HTMLTextAreaElement : HTMLElement {
boolean reportValidity();
undefined setCustomValidity(DOMString error);
- // FIXME: readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
// FIXME: undefined select();
attribute unsigned long selectionStart;