script/dom/
radionodelist.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 stylo_atoms::Atom;
7
8use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
9use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
10use crate::dom::bindings::codegen::Bindings::RadioNodeListBinding::RadioNodeListMethods;
11use crate::dom::bindings::inheritance::Castable;
12use crate::dom::bindings::reflector::reflect_dom_object;
13use crate::dom::bindings::root::DomRoot;
14use crate::dom::bindings::str::DOMString;
15use crate::dom::html::htmlformelement::HTMLFormElement;
16use crate::dom::html::input_element::HTMLInputElement;
17use crate::dom::input_element::input_type::InputType;
18use crate::dom::node::Node;
19use crate::dom::nodelist::{NodeList, NodeListType, RadioList, RadioListMode};
20use crate::dom::window::Window;
21use crate::script_runtime::CanGc;
22
23#[dom_struct]
24pub(crate) struct RadioNodeList {
25    node_list: NodeList,
26}
27
28impl RadioNodeList {
29    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
30    fn new_inherited(list_type: NodeListType) -> RadioNodeList {
31        RadioNodeList {
32            node_list: NodeList::new_inherited(list_type),
33        }
34    }
35
36    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
37    pub(crate) fn new(
38        window: &Window,
39        list_type: NodeListType,
40        can_gc: CanGc,
41    ) -> DomRoot<RadioNodeList> {
42        reflect_dom_object(
43            Box::new(RadioNodeList::new_inherited(list_type)),
44            window,
45            can_gc,
46        )
47    }
48
49    pub(crate) fn new_controls_except_image_inputs(
50        window: &Window,
51        form: &HTMLFormElement,
52        name: &Atom,
53        can_gc: CanGc,
54    ) -> DomRoot<RadioNodeList> {
55        RadioNodeList::new(
56            window,
57            NodeListType::Radio(RadioList::new(
58                form,
59                RadioListMode::ControlsExceptImageInputs,
60                name.clone(),
61            )),
62            can_gc,
63        )
64    }
65
66    pub(crate) fn new_images(
67        window: &Window,
68        form: &HTMLFormElement,
69        name: &Atom,
70        can_gc: CanGc,
71    ) -> DomRoot<RadioNodeList> {
72        RadioNodeList::new(
73            window,
74            NodeListType::Radio(RadioList::new(form, RadioListMode::Images, name.clone())),
75            can_gc,
76        )
77    }
78}
79
80impl RadioNodeListMethods<crate::DomTypeHolder> for RadioNodeList {
81    // https://dom.spec.whatwg.org/#dom-nodelist-length
82    /// <https://github.com/servo/servo/issues/5875>
83    fn Length(&self) -> u32 {
84        self.node_list.Length()
85    }
86
87    /// <https://html.spec.whatwg.org/multipage/#dom-radionodelist-value>
88    fn Value(&self) -> DOMString {
89        self.upcast::<NodeList>()
90            .iter()
91            .find_map(|node| {
92                // Step 1
93                node.downcast::<HTMLInputElement>().and_then(|input| {
94                    if matches!(*input.input_type(), InputType::Radio(_)) && input.Checked() {
95                        // Step 3-4
96                        let value = input.Value();
97                        Some(if value.is_empty() {
98                            DOMString::from("on")
99                        } else {
100                            value
101                        })
102                    } else {
103                        None
104                    }
105                })
106            })
107            // Step 2
108            .unwrap_or(DOMString::from(""))
109    }
110
111    /// <https://html.spec.whatwg.org/multipage/#dom-radionodelist-value>
112    fn SetValue(&self, value: DOMString, can_gc: CanGc) {
113        for node in self.upcast::<NodeList>().iter() {
114            // Step 1
115            if let Some(input) = node.downcast::<HTMLInputElement>() {
116                match *input.input_type() {
117                    InputType::Radio(_) if value == *"on" => {
118                        // Step 2
119                        let val = input.Value();
120                        if val.is_empty() || val == value {
121                            input.SetChecked(true, can_gc);
122                            return;
123                        }
124                    },
125                    InputType::Radio(_) => {
126                        // Step 2
127                        if input.Value() == value {
128                            input.SetChecked(true, can_gc);
129                            return;
130                        }
131                    },
132                    _ => {},
133                }
134            }
135        }
136    }
137
138    // FIXME: This shouldn't need to be implemented here since NodeList (the parent of
139    // RadioNodeList) implements IndexedGetter.
140    // https://github.com/servo/servo/issues/5875
141    //
142    /// <https://dom.spec.whatwg.org/#dom-nodelist-item>
143    fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Node>> {
144        self.node_list.IndexedGetter(index)
145    }
146}