Skip to main content

script/dom/html/form_controls/
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::context::{JSContext, NoGC};
10use js::rust::HandleObject;
11use stylo_dom::ElementState;
12
13use crate::dom::activation::Activatable;
14use crate::dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding::HTMLFieldSetElementMethods;
15use crate::dom::bindings::inheritance::Castable;
16use crate::dom::bindings::root::{DomRoot, MutNullableDom};
17use crate::dom::bindings::str::DOMString;
18use crate::dom::document::Document;
19use crate::dom::element::attributes::storage::AttrRef;
20use crate::dom::element::{AttributeMutation, Element};
21use crate::dom::html::htmlcollection::HTMLCollection;
22use crate::dom::html::htmlelement::HTMLElement;
23use crate::dom::html::htmlformelement::{FormControl, HTMLFormElement};
24use crate::dom::html::htmllegendelement::HTMLLegendElement;
25use crate::dom::iterators::ShadowIncluding;
26use crate::dom::node::virtualmethods::VirtualMethods;
27use crate::dom::node::{Node, NodeTraits};
28use crate::dom::validation::Validatable;
29use crate::dom::validitystate::ValidityState;
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        cx: &mut js::context::JSContext,
58        local_name: LocalName,
59        prefix: Option<Prefix>,
60        document: &Document,
61        proto: Option<HandleObject>,
62    ) -> DomRoot<HTMLFieldSetElement> {
63        Node::reflect_node_with_proto(
64            cx,
65            Box::new(HTMLFieldSetElement::new_inherited(
66                local_name, prefix, document,
67            )),
68            document,
69            proto,
70        )
71    }
72
73    pub(crate) fn update_validity(&self, cx: &mut JSContext) {
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(cx, false));
79
80        self.upcast::<Element>()
81            .set_state(cx.no_gc(), ElementState::VALID, !has_invalid_child);
82        self.upcast::<Element>()
83            .set_state(cx.no_gc(), 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, cx: &mut js::context::JSContext) -> DomRoot<HTMLCollection> {
90        HTMLCollection::new_with_filter_fn(cx, &self.owner_window(), self.upcast(), |element, _| {
91            element
92                .downcast::<HTMLElement>()
93                .is_some_and(HTMLElement::is_listed_element)
94        })
95    }
96
97    // https://html.spec.whatwg.org/multipage/#dom-fieldset-disabled
98    make_bool_getter!(Disabled, "disabled");
99
100    // https://html.spec.whatwg.org/multipage/#dom-fieldset-disabled
101    make_bool_setter!(SetDisabled, "disabled");
102
103    // https://html.spec.whatwg.org/multipage/#dom-fe-name
104    make_atomic_setter!(SetName, "name");
105
106    // https://html.spec.whatwg.org/multipage/#dom-fe-name
107    make_getter!(Name, "name");
108
109    /// <https://html.spec.whatwg.org/multipage/#dom-fae-form>
110    fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
111        self.form_owner()
112    }
113
114    /// <https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate>
115    fn WillValidate(&self, no_gc: &NoGC) -> bool {
116        self.is_instance_validatable(no_gc)
117    }
118
119    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validity>
120    fn Validity(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
121        self.validity_state(cx)
122    }
123
124    /// <https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity>
125    fn CheckValidity(&self, cx: &mut JSContext) -> bool {
126        self.check_validity(cx)
127    }
128
129    /// <https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity>
130    fn ReportValidity(&self, cx: &mut JSContext) -> bool {
131        self.report_validity(cx)
132    }
133
134    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage>
135    fn ValidationMessage(&self, cx: &mut JSContext) -> DOMString {
136        self.validation_message(cx)
137    }
138
139    /// <https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity>
140    fn SetCustomValidity(&self, cx: &mut JSContext, error: DOMString) {
141        self.validity_state(cx).set_custom_error_message(cx, error);
142    }
143
144    /// <https://html.spec.whatwg.org/multipage/#dom-fieldset-type>
145    fn Type(&self) -> DOMString {
146        DOMString::from_static("fieldset")
147    }
148}
149
150impl VirtualMethods for HTMLFieldSetElement {
151    fn super_type(&self) -> Option<&dyn VirtualMethods> {
152        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
153    }
154
155    fn attribute_mutated(
156        &self,
157        cx: &mut js::context::JSContext,
158        attr: AttrRef<'_>,
159        mutation: AttributeMutation,
160    ) {
161        self.super_type()
162            .unwrap()
163            .attribute_mutated(cx, attr, mutation);
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(cx.no_gc(), disabled_state);
177                element.set_enabled_state(cx.no_gc(), !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_map(DomRoot::downcast::<HTMLElement>)
193                });
194                for field in fields {
195                    if !field.is_disableable_form_control() {
196                        continue;
197                    }
198                    if disabled_state {
199                        field.set_disabled_state_and_notify(cx, disabled_state);
200                    } else if field.as_element().disabled_state() {
201                        field.maybe_update_form_control_disabled(cx);
202                    }
203                }
204            },
205            local_name!("form") => {
206                self.form_attribute_mutated(cx, mutation);
207            },
208            _ => {},
209        }
210    }
211}
212
213impl FormControl for HTMLFieldSetElement {
214    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
215        self.form_owner.get()
216    }
217
218    fn set_form_owner(&self, _cx: &mut JSContext, form: Option<&HTMLFormElement>) {
219        self.form_owner.set(form);
220    }
221
222    fn to_html_element(&self) -> &HTMLElement {
223        self.upcast::<HTMLElement>()
224    }
225}
226
227impl Validatable for HTMLFieldSetElement {
228    fn as_element(&self) -> &Element {
229        self.upcast()
230    }
231
232    fn validity_state(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
233        self.validity_state
234            .or_init(|| ValidityState::new(cx, &self.owner_window(), self.upcast()))
235    }
236
237    fn is_instance_validatable(&self, _no_gc: &NoGC) -> bool {
238        // fieldset is not a submittable element (https://html.spec.whatwg.org/multipage/#category-submit)
239        false
240    }
241}