Skip to main content

script/dom/form/
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, NoGC};
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::form_controls::htmlinputelement::HTMLInputElement;
17use crate::dom::html::form_controls::input_type::InputType;
18use crate::dom::html::htmlformelement::HTMLFormElement;
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, _cx: &JSContext) -> u32 {
84        self.node_list.Length()
85    }
86
87    /// <https://html.spec.whatwg.org/multipage/#dom-radionodelist-value>
88    fn Value(&self, no_gc: &NoGC) -> DOMString {
89        self.upcast::<NodeList>()
90            .iter(no_gc)
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        let node_list = self.upcast::<NodeList>();
114        // Inlining `node_list.iter()` so `cx` doesn’t stay borrowed
115        for index in 0..node_list.Length() {
116            let Some(node) = node_list.Item(cx, index) else {
117                continue;
118            };
119            // Step 1
120            if let Some(input) = node.downcast::<HTMLInputElement>() {
121                match *input.input_type() {
122                    InputType::Radio(_) if value == *"on" => {
123                        // Step 2
124                        let val = input.Value();
125                        if val.is_empty() || val == value {
126                            input.SetChecked(cx, true);
127                            return;
128                        }
129                    },
130                    InputType::Radio(_) if input.Value() == value => {
131                        // Step 2
132                        input.SetChecked(cx, true);
133                        return;
134                    },
135                    _ => {},
136                }
137            }
138        }
139    }
140
141    // FIXME: This shouldn't need to be implemented here since NodeList (the parent of
142    // RadioNodeList) implements IndexedGetter.
143    // https://github.com/servo/servo/issues/5875
144    //
145    /// <https://dom.spec.whatwg.org/#dom-nodelist-item>
146    fn IndexedGetter(&self, cx: &NoGC, index: u32) -> Option<DomRoot<Node>> {
147        self.node_list.IndexedGetter(cx, index)
148    }
149}