Skip to main content

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