LibWeb: Implement the labels attribute for all labelable elements

This returns a `NodeList` of all the labels associated with the given
element.
This commit is contained in:
Tim Ledbetter 2024-05-18 14:10:00 +01:00 committed by Andreas Kling
commit 2447a25753
Notes: sideshowbarker 2024-07-16 20:39:14 +09:00
11 changed files with 89 additions and 7 deletions

View file

@ -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

View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<script>
test(() => {
const labelableElements = ["input", "meter", "output", "progress", "select", "textarea"];
for (const tagName of labelableElements) {
const element = document.createElement(tagName);
element.id = `${tagName}Id`;
const label = document.createElement("label")
label.htmlFor = `${tagName}Id`;
document.body.appendChild(label);
document.body.appendChild(element);
const labels = element.labels;
println(`${tagName}.labels.length: ${labels.length}`);
println(`${tagName}.labels[0] === label: ${labels[0] === label}`);
println(`${tagName}.labels always returns the same object: ${labels === element.labels}`);
document.body.removeChild(label);
document.body.removeChild(element);
}
const inputElement = document.createElement("input");
inputElement.type = "hidden";
inputElement.id = "inputId"
const label = document.createElement("label")
label.htmlFor = 'inputId';
document.body.appendChild(label);
document.body.appendChild(inputElement);
println(`input.labels returns null if input type is hidden: ${inputElement.labels === null}`);
inputElement.type = "text";
const labels = inputElement.labels;
println(`input.labels.length after input type is changed from hidden: ${labels.length}`);
println(`input.labels[0] === label after input type is changed from hidden: ${labels[0] === label}`);
document.body.removeChild(label);
document.body.removeChild(inputElement);
});
</script>

View file

@ -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;

View file

@ -10,6 +10,7 @@
#include <LibWeb/Bindings/HTMLElementPrototype.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/IDLEventListener.h>
#include <LibWeb/DOM/LiveNodeList.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/DOMStringMap.h>
@ -20,6 +21,7 @@
#include <LibWeb/HTML/HTMLBaseElement.h>
#include <LibWeb/HTML/HTMLBodyElement.h>
#include <LibWeb/HTML/HTMLElement.h>
#include <LibWeb/HTML/HTMLLabelElement.h>
#include <LibWeb/HTML/NavigableContainer.h>
#include <LibWeb/HTML/VisibilityState.h>
#include <LibWeb/HTML/Window.h>
@ -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<DOM::NodeList> 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<HTMLLabelElement>(node) && verify_cast<HTMLLabelElement>(node).control() == this;
});
}
return m_labels;
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-click
void HTMLElement::click()
{

View file

@ -68,6 +68,8 @@ public:
// https://html.spec.whatwg.org/multipage/forms.html#category-label
virtual bool is_labelable() const { return false; }
JS::GCPtr<DOM::NodeList> labels();
virtual Optional<ARIA::Role> default_role() const override;
String get_an_elements_target() const;
@ -93,6 +95,8 @@ private:
JS::GCPtr<DOMStringMap> m_dataset;
JS::GCPtr<DOM::NodeList> m_labels;
enum class ContentEditableState {
True,
False,

View file

@ -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;

View file

@ -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;
};

View file

@ -21,5 +21,5 @@ interface HTMLOutputElement : HTMLElement {
// FIXME: boolean reportValidity();
// FIXME: undefined setCustomValidity(DOMString error);
// FIXME: readonly attribute NodeList labels;
readonly attribute NodeList labels;
};

View file

@ -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;
};

View file

@ -37,5 +37,5 @@ interface HTMLSelectElement : HTMLElement {
// FIXME: boolean reportValidity();
// FIXME: undefined setCustomValidity(DOMString error);
// FIXME: readonly attribute NodeList labels;
readonly attribute NodeList labels;
};

View file

@ -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;