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