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