Skip to main content

script/dom/html/form_controls/
htmllabelelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use dom_struct::dom_struct;
6use html5ever::{LocalName, Prefix, local_name};
7use js::context::NoGC;
8use js::rust::HandleObject;
9use style::attr::AttrValue;
10
11use crate::dom::activation::Activatable;
12use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
13use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
14use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
15use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
16use crate::dom::bindings::inheritance::Castable;
17use crate::dom::bindings::root::DomRoot;
18use crate::dom::bindings::str::DOMString;
19use crate::dom::document::Document;
20use crate::dom::element::attributes::storage::AttrRef;
21use crate::dom::element::{AttributeMutation, Element};
22use crate::dom::event::Event;
23use crate::dom::eventtarget::EventTarget;
24use crate::dom::html::htmlelement::HTMLElement;
25use crate::dom::html::htmlformelement::{FormControlElementHelpers, HTMLFormElement};
26use crate::dom::iterators::ShadowIncluding;
27use crate::dom::node::Node;
28use crate::dom::node::virtualmethods::VirtualMethods;
29
30#[dom_struct]
31pub(crate) struct HTMLLabelElement {
32    htmlelement: HTMLElement,
33}
34
35impl HTMLLabelElement {
36    fn new_inherited(
37        local_name: LocalName,
38        prefix: Option<Prefix>,
39        document: &Document,
40    ) -> HTMLLabelElement {
41        HTMLLabelElement {
42            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
43        }
44    }
45
46    pub(crate) fn new(
47        cx: &mut js::context::JSContext,
48        local_name: LocalName,
49        prefix: Option<Prefix>,
50        document: &Document,
51        proto: Option<HandleObject>,
52    ) -> DomRoot<HTMLLabelElement> {
53        Node::reflect_node_with_proto(
54            cx,
55            Box::new(HTMLLabelElement::new_inherited(
56                local_name, prefix, document,
57            )),
58            document,
59            proto,
60        )
61    }
62}
63
64impl Activatable for HTMLLabelElement {
65    fn as_element(&self) -> &Element {
66        self.upcast::<Element>()
67    }
68
69    fn is_instance_activatable(&self) -> bool {
70        true
71    }
72
73    // https://html.spec.whatwg.org/multipage/#the-label-element:activation_behaviour
74    // Basically this is telling us that if activation bubbles up to the label
75    // at all, we are free to do an implementation-dependent thing;
76    // firing a click event is an example, and the precise details of that
77    // click event (e.g. isTrusted) are not specified.
78    fn activation_behavior(
79        &self,
80        cx: &mut js::context::JSContext,
81        _event: &Event,
82        _target: &EventTarget,
83    ) {
84        if let Some(e) = self.GetControl(cx.no_gc()) {
85            e.Click(cx);
86        }
87    }
88}
89
90impl HTMLLabelElementMethods<crate::DomTypeHolder> for HTMLLabelElement {
91    /// <https://html.spec.whatwg.org/multipage/#dom-label-form>
92    fn GetForm(&self, no_gc: &NoGC) -> Option<DomRoot<HTMLFormElement>> {
93        // > The form IDL attribute must run the following steps:
94        // > 1. If the label element has no labeled control, then return null.
95        // > 2. If the label element's labeled control is not a form-associated element, then
96        // >    return null.
97        // > 3. Return the label element's labeled control's form owner (which can still be
98        // >    null).
99        self.GetControl(no_gc)
100            .map(DomRoot::upcast::<Element>)
101            .and_then(|element| {
102                element
103                    .as_maybe_form_control()
104                    .and_then(|control| control.form_owner())
105            })
106    }
107
108    // https://html.spec.whatwg.org/multipage/#dom-label-htmlfor
109    make_getter!(HtmlFor, "for");
110
111    // https://html.spec.whatwg.org/multipage/#dom-label-htmlfor
112    make_atomic_setter!(SetHtmlFor, "for");
113
114    /// <https://html.spec.whatwg.org/multipage/#dom-label-control>
115    fn GetControl(&self, no_gc: &NoGC) -> Option<DomRoot<HTMLElement>> {
116        let Some(for_value) = self
117            .upcast::<Element>()
118            .get_attribute_string_value(&local_name!("for"))
119        else {
120            return self.first_labelable_descendant();
121        };
122
123        // "If the attribute is specified and there is an element in the tree
124        // whose ID is equal to the value of the for attribute, and the first
125        // such element in tree order is a labelable element, then that
126        // element is the label element's labeled control."
127        // Two subtle points here: we need to search the _tree_, which is
128        // not necessarily the document if we're detached from the document,
129        // and we only consider one element even if a later element with
130        // the same ID is labelable.
131
132        let maybe_found = self
133            .upcast::<Node>()
134            .GetRootNode(&GetRootNodeOptions::empty())
135            .traverse_preorder_unrooted(no_gc, ShadowIncluding::No)
136            .find_map(|e| {
137                if let Some(htmle) = e.downcast::<HTMLElement>() {
138                    if htmle.upcast::<Element>().Id() == for_value {
139                        Some(DomRoot::from_ref(htmle))
140                    } else {
141                        None
142                    }
143                } else {
144                    None
145                }
146            });
147        // We now have the element that we would return, but only return it
148        // if it's labelable.
149        if let Some(ref maybe_labelable) = maybe_found &&
150            maybe_labelable.is_labelable_element()
151        {
152            return maybe_found;
153        }
154        None
155    }
156}
157
158impl VirtualMethods for HTMLLabelElement {
159    fn super_type(&self) -> Option<&dyn VirtualMethods> {
160        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
161    }
162
163    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
164        match name {
165            &local_name!("for") => AttrValue::from_atomic(value.into()),
166            _ => self
167                .super_type()
168                .unwrap()
169                .parse_plain_attribute(name, value),
170        }
171    }
172
173    fn attribute_mutated(
174        &self,
175        cx: &mut js::context::JSContext,
176        attr: AttrRef<'_>,
177        mutation: AttributeMutation,
178    ) {
179        self.super_type()
180            .unwrap()
181            .attribute_mutated(cx, attr, mutation);
182    }
183}
184
185impl HTMLLabelElement {
186    pub(crate) fn first_labelable_descendant(&self) -> Option<DomRoot<HTMLElement>> {
187        self.upcast::<Node>()
188            .traverse_preorder(ShadowIncluding::No)
189            .filter_map(DomRoot::downcast::<HTMLElement>)
190            .find(|elem| elem.is_labelable_element())
191    }
192}