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::{FormControl, FormControlElementHelpers, HTMLFormElement};
25use crate::dom::iterators::ShadowIncluding;
26use crate::dom::node::Node;
27use crate::dom::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-fae-form>
91    fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
92        self.form_owner()
93    }
94
95    // https://html.spec.whatwg.org/multipage/#dom-label-htmlfor
96    make_getter!(HtmlFor, "for");
97
98    // https://html.spec.whatwg.org/multipage/#dom-label-htmlfor
99    make_atomic_setter!(SetHtmlFor, "for");
100
101    /// <https://html.spec.whatwg.org/multipage/#dom-label-control>
102    fn GetControl(&self) -> Option<DomRoot<HTMLElement>> {
103        let Some(for_value) = self
104            .upcast::<Element>()
105            .get_attribute_string_value(&local_name!("for"))
106        else {
107            return self.first_labelable_descendant();
108        };
109
110        // "If the attribute is specified and there is an element in the tree
111        // whose ID is equal to the value of the for attribute, and the first
112        // such element in tree order is a labelable element, then that
113        // element is the label element's labeled control."
114        // Two subtle points here: we need to search the _tree_, which is
115        // not necessarily the document if we're detached from the document,
116        // and we only consider one element even if a later element with
117        // the same ID is labelable.
118
119        let maybe_found = self
120            .upcast::<Node>()
121            .GetRootNode(&GetRootNodeOptions::empty())
122            .traverse_preorder(ShadowIncluding::No)
123            .find_map(|e| {
124                if let Some(htmle) = e.downcast::<HTMLElement>() {
125                    if htmle.upcast::<Element>().Id() == for_value {
126                        Some(DomRoot::from_ref(htmle))
127                    } else {
128                        None
129                    }
130                } else {
131                    None
132                }
133            });
134        // We now have the element that we would return, but only return it
135        // if it's labelable.
136        if let Some(ref maybe_labelable) = maybe_found &&
137            maybe_labelable.is_labelable_element()
138        {
139            return maybe_found;
140        }
141        None
142    }
143}
144
145impl VirtualMethods for HTMLLabelElement {
146    fn super_type(&self) -> Option<&dyn VirtualMethods> {
147        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
148    }
149
150    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
151        match name {
152            &local_name!("for") => AttrValue::from_atomic(value.into()),
153            _ => self
154                .super_type()
155                .unwrap()
156                .parse_plain_attribute(name, value),
157        }
158    }
159
160    fn attribute_mutated(
161        &self,
162        cx: &mut js::context::JSContext,
163        attr: AttrRef<'_>,
164        mutation: AttributeMutation,
165    ) {
166        self.super_type()
167            .unwrap()
168            .attribute_mutated(cx, attr, mutation);
169        if *attr.local_name() == local_name!("form") {
170            self.form_attribute_mutated(cx, mutation);
171        }
172    }
173}
174
175impl HTMLLabelElement {
176    pub(crate) fn first_labelable_descendant(&self) -> Option<DomRoot<HTMLElement>> {
177        self.upcast::<Node>()
178            .traverse_preorder(ShadowIncluding::No)
179            .filter_map(DomRoot::downcast::<HTMLElement>)
180            .find(|elem| elem.is_labelable_element())
181    }
182}
183
184impl FormControl for HTMLLabelElement {
185    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
186        self.GetControl()
187            .map(DomRoot::upcast::<Element>)
188            .and_then(|elem| {
189                elem.as_maybe_form_control()
190                    .and_then(|control| control.form_owner())
191            })
192    }
193
194    fn set_form_owner(&self, _: Option<&HTMLFormElement>) {
195        // Label is a special case for form owner, it reflects its control's
196        // form owner. Therefore it doesn't hold form owner itself.
197    }
198
199    fn to_element(&self) -> &Element {
200        self.upcast::<Element>()
201    }
202}