Skip to main content

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 js::context::JSContext;
7use script_bindings::reflector::reflect_dom_object_with_cx;
8use script_bindings::root::DomRoot;
9use stylo_atoms::Atom;
10
11use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
12use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
13use crate::dom::bindings::codegen::Bindings::RadioNodeListBinding::RadioNodeListMethods;
14use crate::dom::bindings::inheritance::Castable;
15use crate::dom::bindings::str::DOMString;
16use crate::dom::html::htmlformelement::HTMLFormElement;
17use crate::dom::html::input_element::HTMLInputElement;
18use crate::dom::input_element::input_type::InputType;
19use crate::dom::node::Node;
20use crate::dom::nodelist::{NodeList, NodeListType, RadioList, RadioListMode};
21use crate::dom::window::Window;
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        cx: &mut JSContext,
39        window: &Window,
40        list_type: NodeListType,
41    ) -> DomRoot<RadioNodeList> {
42        reflect_dom_object_with_cx(
43            Box::new(RadioNodeList::new_inherited(list_type)),
44            window,
45            cx,
46        )
47    }
48
49    pub(crate) fn new_controls_except_image_inputs(
50        cx: &mut JSContext,
51        window: &Window,
52        form: &HTMLFormElement,
53        name: &Atom,
54    ) -> DomRoot<RadioNodeList> {
55        RadioNodeList::new(
56            cx,
57            window,
58            NodeListType::Radio(RadioList::new(
59                form,
60                RadioListMode::ControlsExceptImageInputs,
61                name.clone(),
62            )),
63        )
64    }
65
66    pub(crate) fn new_images(
67        cx: &mut JSContext,
68        window: &Window,
69        form: &HTMLFormElement,
70        name: &Atom,
71    ) -> DomRoot<RadioNodeList> {
72        RadioNodeList::new(
73            cx,
74            window,
75            NodeListType::Radio(RadioList::new(form, RadioListMode::Images, name.clone())),
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, cx: &mut JSContext, value: DOMString) {
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(cx, true);
122                            return;
123                        }
124                    },
125                    InputType::Radio(_) if input.Value() == value => {
126                        // Step 2
127                        input.SetChecked(cx, true);
128                        return;
129                    },
130                    _ => {},
131                }
132            }
133        }
134    }
135
136    // FIXME: This shouldn't need to be implemented here since NodeList (the parent of
137    // RadioNodeList) implements IndexedGetter.
138    // https://github.com/servo/servo/issues/5875
139    //
140    /// <https://dom.spec.whatwg.org/#dom-nodelist-item>
141    fn IndexedGetter(&self, index: u32) -> Option<DomRoot<Node>> {
142        self.node_list.IndexedGetter(index)
143    }
144}