script/dom/html/
htmlfieldsetelement.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 std::default::Default;
6
7use dom_struct::dom_struct;
8use html5ever::{LocalName, Prefix, local_name};
9use js::rust::HandleObject;
10use stylo_dom::ElementState;
11
12use crate::dom::attr::Attr;
13use crate::dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding::HTMLFieldSetElementMethods;
14use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
15use crate::dom::bindings::root::{DomRoot, MutNullableDom};
16use crate::dom::bindings::str::DOMString;
17use crate::dom::customelementregistry::CallbackReaction;
18use crate::dom::document::Document;
19use crate::dom::element::{AttributeMutation, Element};
20use crate::dom::html::htmlcollection::HTMLCollection;
21use crate::dom::html::htmlelement::HTMLElement;
22use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
23use crate::dom::html::htmllegendelement::HTMLLegendElement;
24use crate::dom::node::{Node, NodeTraits, ShadowIncluding};
25use crate::dom::validation::Validatable;
26use crate::dom::validitystate::ValidityState;
27use crate::dom::virtualmethods::VirtualMethods;
28use crate::script_runtime::CanGc;
29use crate::script_thread::ScriptThread;
30
31#[dom_struct]
32pub(crate) struct HTMLFieldSetElement {
33    htmlelement: HTMLElement,
34    form_owner: MutNullableDom<HTMLFormElement>,
35    validity_state: MutNullableDom<ValidityState>,
36}
37
38impl HTMLFieldSetElement {
39    fn new_inherited(
40        local_name: LocalName,
41        prefix: Option<Prefix>,
42        document: &Document,
43    ) -> HTMLFieldSetElement {
44        HTMLFieldSetElement {
45            htmlelement: HTMLElement::new_inherited_with_state(
46                ElementState::ENABLED | ElementState::VALID,
47                local_name,
48                prefix,
49                document,
50            ),
51            form_owner: Default::default(),
52            validity_state: Default::default(),
53        }
54    }
55
56    pub(crate) fn new(
57        local_name: LocalName,
58        prefix: Option<Prefix>,
59        document: &Document,
60        proto: Option<HandleObject>,
61        can_gc: CanGc,
62    ) -> DomRoot<HTMLFieldSetElement> {
63        Node::reflect_node_with_proto(
64            Box::new(HTMLFieldSetElement::new_inherited(
65                local_name, prefix, document,
66            )),
67            document,
68            proto,
69            can_gc,
70        )
71    }
72
73    pub(crate) fn update_validity(&self, can_gc: CanGc) {
74        let has_invalid_child = self
75            .upcast::<Node>()
76            .traverse_preorder(ShadowIncluding::No)
77            .flat_map(DomRoot::downcast::<Element>)
78            .any(|element| element.is_invalid(false, can_gc));
79
80        self.upcast::<Element>()
81            .set_state(ElementState::VALID, !has_invalid_child);
82        self.upcast::<Element>()
83            .set_state(ElementState::INVALID, has_invalid_child);
84    }
85}
86
87impl HTMLFieldSetElementMethods<crate::DomTypeHolder> for HTMLFieldSetElement {
88    /// <https://html.spec.whatwg.org/multipage/#dom-fieldset-elements>
89    fn Elements(&self, can_gc: CanGc) -> DomRoot<HTMLCollection> {
90        HTMLCollection::new_with_filter_fn(
91            &self.owner_window(),
92            self.upcast(),
93            |element, _| {
94                element
95                    .downcast::<HTMLElement>()
96                    .is_some_and(HTMLElement::is_listed_element)
97            },
98            can_gc,
99        )
100    }
101
102    // https://html.spec.whatwg.org/multipage/#dom-fieldset-disabled
103    make_bool_getter!(Disabled, "disabled");
104
105    // https://html.spec.whatwg.org/multipage/#dom-fieldset-disabled
106    make_bool_setter!(SetDisabled, "disabled");
107
108    // https://html.spec.whatwg.org/multipage/#dom-fe-name
109    make_atomic_setter!(SetName, "name");
110
111    // https://html.spec.whatwg.org/multipage/#dom-fe-name
112    make_getter!(Name, "name");
113
114    /// <https://html.spec.whatwg.org/multipage/#dom-fae-form>
115    fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
116        self.form_owner()
117    }
118
119    /// <https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate>
120    fn WillValidate(&self) -> bool {
121        self.is_instance_validatable()
122    }
123
124    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validity>
125    fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
126        self.validity_state(can_gc)
127    }
128
129    /// <https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity>
130    fn CheckValidity(&self, can_gc: CanGc) -> bool {
131        self.check_validity(can_gc)
132    }
133
134    /// <https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity>
135    fn ReportValidity(&self, can_gc: CanGc) -> bool {
136        self.report_validity(can_gc)
137    }
138
139    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage>
140    fn ValidationMessage(&self) -> DOMString {
141        self.validation_message()
142    }
143
144    /// <https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity>
145    fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
146        self.validity_state(can_gc).set_custom_error_message(error);
147    }
148
149    /// <https://html.spec.whatwg.org/multipage/#dom-fieldset-type>
150    fn Type(&self) -> DOMString {
151        DOMString::from_string(String::from("fieldset"))
152    }
153}
154
155impl VirtualMethods for HTMLFieldSetElement {
156    fn super_type(&self) -> Option<&dyn VirtualMethods> {
157        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
158    }
159
160    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
161        self.super_type()
162            .unwrap()
163            .attribute_mutated(attr, mutation, can_gc);
164        match *attr.local_name() {
165            local_name!("disabled") => {
166                let disabled_state = match mutation {
167                    AttributeMutation::Set(None, _) => true,
168                    AttributeMutation::Set(Some(_), _) => {
169                        // Fieldset was already disabled before.
170                        return;
171                    },
172                    AttributeMutation::Removed => false,
173                };
174                let node = self.upcast::<Node>();
175                let element = self.upcast::<Element>();
176                element.set_disabled_state(disabled_state);
177                element.set_enabled_state(!disabled_state);
178                let mut found_legend = false;
179                let children = node.children().filter(|node| {
180                    if found_legend {
181                        true
182                    } else if node.is::<HTMLLegendElement>() {
183                        found_legend = true;
184                        false
185                    } else {
186                        true
187                    }
188                });
189                let fields = children.flat_map(|child| {
190                    child
191                        .traverse_preorder(ShadowIncluding::No)
192                        .filter(|descendant| match descendant.type_id() {
193                            NodeTypeId::Element(ElementTypeId::HTMLElement(
194                                HTMLElementTypeId::HTMLButtonElement |
195                                HTMLElementTypeId::HTMLInputElement |
196                                HTMLElementTypeId::HTMLSelectElement |
197                                HTMLElementTypeId::HTMLTextAreaElement,
198                            )) => true,
199                            NodeTypeId::Element(ElementTypeId::HTMLElement(
200                                HTMLElementTypeId::HTMLElement,
201                            )) => descendant
202                                .downcast::<HTMLElement>()
203                                .unwrap()
204                                .is_form_associated_custom_element(),
205                            _ => false,
206                        })
207                });
208                if disabled_state {
209                    for field in fields {
210                        let element = field.downcast::<Element>().unwrap();
211                        if element.enabled_state() {
212                            element.set_disabled_state(true);
213                            element.set_enabled_state(false);
214                            if element
215                                .downcast::<HTMLElement>()
216                                .is_some_and(|h| h.is_form_associated_custom_element())
217                            {
218                                ScriptThread::enqueue_callback_reaction(
219                                    element,
220                                    CallbackReaction::FormDisabled(true),
221                                    None,
222                                );
223                            }
224                        }
225                        element.update_sequentially_focusable_status(can_gc);
226                    }
227                } else {
228                    for field in fields {
229                        let element = field.downcast::<Element>().unwrap();
230                        if element.disabled_state() {
231                            element.check_disabled_attribute();
232                            element.check_ancestors_disabled_state_for_form_control();
233                            // Fire callback only if this has actually enabled the custom element
234                            if element.enabled_state() &&
235                                element
236                                    .downcast::<HTMLElement>()
237                                    .is_some_and(|h| h.is_form_associated_custom_element())
238                            {
239                                ScriptThread::enqueue_callback_reaction(
240                                    element,
241                                    CallbackReaction::FormDisabled(false),
242                                    None,
243                                );
244                            }
245                        }
246                        element.update_sequentially_focusable_status(can_gc);
247                    }
248                }
249                element.update_sequentially_focusable_status(can_gc);
250            },
251            local_name!("form") => {
252                self.form_attribute_mutated(mutation, can_gc);
253            },
254            _ => {},
255        }
256    }
257}
258
259impl FormControl for HTMLFieldSetElement {
260    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
261        self.form_owner.get()
262    }
263
264    fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
265        self.form_owner.set(form);
266    }
267
268    fn to_element(&self) -> &Element {
269        self.upcast::<Element>()
270    }
271}
272
273impl Validatable for HTMLFieldSetElement {
274    fn as_element(&self) -> &Element {
275        self.upcast()
276    }
277
278    fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
279        self.validity_state
280            .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
281    }
282
283    fn is_instance_validatable(&self) -> bool {
284        // fieldset is not a submittable element (https://html.spec.whatwg.org/multipage/#category-submit)
285        false
286    }
287}