script/dom/html/
htmlbuttonelement.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::cell::Cell;
6use std::default::Default;
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, local_name};
10use js::rust::HandleObject;
11use stylo_dom::ElementState;
12
13use crate::dom::activation::Activatable;
14use crate::dom::attr::Attr;
15use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
16use crate::dom::bindings::inheritance::Castable;
17use crate::dom::bindings::root::{DomRoot, MutNullableDom};
18use crate::dom::bindings::str::DOMString;
19use crate::dom::document::Document;
20use crate::dom::element::{AttributeMutation, Element};
21use crate::dom::event::Event;
22use crate::dom::eventtarget::EventTarget;
23use crate::dom::html::htmlelement::HTMLElement;
24use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
25use crate::dom::html::htmlformelement::{
26    FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom,
27    SubmittedFrom,
28};
29use crate::dom::node::{BindContext, Node, NodeTraits, UnbindContext};
30use crate::dom::nodelist::NodeList;
31use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
32use crate::dom::validitystate::{ValidationFlags, ValidityState};
33use crate::dom::virtualmethods::VirtualMethods;
34use crate::script_runtime::CanGc;
35
36#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
37enum ButtonType {
38    Submit,
39    Reset,
40    Button,
41}
42
43#[dom_struct]
44pub(crate) struct HTMLButtonElement {
45    htmlelement: HTMLElement,
46    button_type: Cell<ButtonType>,
47    form_owner: MutNullableDom<HTMLFormElement>,
48    labels_node_list: MutNullableDom<NodeList>,
49    validity_state: MutNullableDom<ValidityState>,
50}
51
52impl HTMLButtonElement {
53    fn new_inherited(
54        local_name: LocalName,
55        prefix: Option<Prefix>,
56        document: &Document,
57    ) -> HTMLButtonElement {
58        HTMLButtonElement {
59            htmlelement: HTMLElement::new_inherited_with_state(
60                ElementState::ENABLED,
61                local_name,
62                prefix,
63                document,
64            ),
65            button_type: Cell::new(ButtonType::Submit),
66            form_owner: Default::default(),
67            labels_node_list: Default::default(),
68            validity_state: Default::default(),
69        }
70    }
71
72    pub(crate) fn new(
73        local_name: LocalName,
74        prefix: Option<Prefix>,
75        document: &Document,
76        proto: Option<HandleObject>,
77        can_gc: CanGc,
78    ) -> DomRoot<HTMLButtonElement> {
79        Node::reflect_node_with_proto(
80            Box::new(HTMLButtonElement::new_inherited(
81                local_name, prefix, document,
82            )),
83            document,
84            proto,
85            can_gc,
86        )
87    }
88
89    #[inline]
90    pub(crate) fn is_submit_button(&self) -> bool {
91        self.button_type.get() == ButtonType::Submit
92    }
93}
94
95impl HTMLButtonElementMethods<crate::DomTypeHolder> for HTMLButtonElement {
96    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
97    make_bool_getter!(Disabled, "disabled");
98
99    // https://html.spec.whatwg.org/multipage/#dom-fe-disabled
100    make_bool_setter!(SetDisabled, "disabled");
101
102    /// <https://html.spec.whatwg.org/multipage/#dom-fae-form>
103    fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
104        self.form_owner()
105    }
106
107    // <https://html.spec.whatwg.org/multipage/#dom-button-type>
108    make_enumerated_getter!(
109        Type,
110        "type",
111        "submit" | "reset" | "button",
112        missing => "submit",
113        invalid => "submit"
114    );
115
116    // https://html.spec.whatwg.org/multipage/#dom-button-type
117    make_setter!(SetType, "type");
118
119    // https://html.spec.whatwg.org/multipage/#dom-fs-formaction
120    make_form_action_getter!(FormAction, "formaction");
121
122    // https://html.spec.whatwg.org/multipage/#dom-fs-formaction
123    make_setter!(SetFormAction, "formaction");
124
125    // https://html.spec.whatwg.org/multipage/#dom-fs-formenctype
126    make_enumerated_getter!(
127        FormEnctype,
128        "formenctype",
129        "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain",
130        invalid => "application/x-www-form-urlencoded"
131    );
132
133    // https://html.spec.whatwg.org/multipage/#dom-fs-formenctype
134    make_setter!(SetFormEnctype, "formenctype");
135
136    // https://html.spec.whatwg.org/multipage/#dom-fs-formmethod
137    make_enumerated_getter!(
138        FormMethod,
139        "formmethod",
140        "get" | "post" | "dialog",
141        invalid => "get"
142    );
143
144    // https://html.spec.whatwg.org/multipage/#dom-fs-formmethod
145    make_setter!(SetFormMethod, "formmethod");
146
147    // https://html.spec.whatwg.org/multipage/#dom-fs-formtarget
148    make_getter!(FormTarget, "formtarget");
149
150    // https://html.spec.whatwg.org/multipage/#dom-fs-formtarget
151    make_setter!(SetFormTarget, "formtarget");
152
153    // https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
154    make_bool_getter!(FormNoValidate, "formnovalidate");
155
156    // https://html.spec.whatwg.org/multipage/#attr-fs-formnovalidate
157    make_bool_setter!(SetFormNoValidate, "formnovalidate");
158
159    // https://html.spec.whatwg.org/multipage/#dom-fe-name
160    make_getter!(Name, "name");
161
162    // https://html.spec.whatwg.org/multipage/#dom-fe-name
163    make_atomic_setter!(SetName, "name");
164
165    // https://html.spec.whatwg.org/multipage/#dom-button-value
166    make_getter!(Value, "value");
167
168    // https://html.spec.whatwg.org/multipage/#dom-button-value
169    make_setter!(SetValue, "value");
170
171    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
172    make_labels_getter!(Labels, labels_node_list);
173
174    /// <https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate>
175    fn WillValidate(&self) -> bool {
176        self.is_instance_validatable()
177    }
178
179    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validity>
180    fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
181        self.validity_state(can_gc)
182    }
183
184    /// <https://html.spec.whatwg.org/multipage/#dom-cva-checkvalidity>
185    fn CheckValidity(&self, can_gc: CanGc) -> bool {
186        self.check_validity(can_gc)
187    }
188
189    /// <https://html.spec.whatwg.org/multipage/#dom-cva-reportvalidity>
190    fn ReportValidity(&self, can_gc: CanGc) -> bool {
191        self.report_validity(can_gc)
192    }
193
194    /// <https://html.spec.whatwg.org/multipage/#dom-cva-validationmessage>
195    fn ValidationMessage(&self) -> DOMString {
196        self.validation_message()
197    }
198
199    /// <https://html.spec.whatwg.org/multipage/#dom-cva-setcustomvalidity>
200    fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
201        self.validity_state(can_gc).set_custom_error_message(error);
202    }
203}
204
205impl HTMLButtonElement {
206    /// <https://html.spec.whatwg.org/multipage/#constructing-the-form-data-set>
207    /// Steps range from 3.1 to 3.7 (specific to HTMLButtonElement)
208    pub(crate) fn form_datum(&self, submitter: Option<FormSubmitterElement>) -> Option<FormDatum> {
209        // Step 3.1: disabled state check is in get_unclean_dataset
210
211        // Step 3.1: only run steps if this is the submitter
212        if let Some(FormSubmitterElement::Button(submitter)) = submitter {
213            if submitter != self {
214                return None;
215            }
216        } else {
217            return None;
218        }
219        // Step 3.2
220        let ty = self.Type();
221        // Step 3.4
222        let name = self.Name();
223
224        if name.is_empty() {
225            // Step 3.1: Must have a name
226            return None;
227        }
228
229        // Step 3.9
230        Some(FormDatum {
231            ty,
232            name,
233            value: FormDatumValue::String(self.Value()),
234        })
235    }
236}
237
238impl VirtualMethods for HTMLButtonElement {
239    fn super_type(&self) -> Option<&dyn VirtualMethods> {
240        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
241    }
242
243    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
244        self.super_type()
245            .unwrap()
246            .attribute_mutated(attr, mutation, can_gc);
247        match *attr.local_name() {
248            local_name!("disabled") => {
249                let el = self.upcast::<Element>();
250                match mutation {
251                    AttributeMutation::Set(Some(_), _) => {},
252                    AttributeMutation::Set(None, _) => {
253                        el.set_disabled_state(true);
254                        el.set_enabled_state(false);
255                    },
256                    AttributeMutation::Removed => {
257                        el.set_disabled_state(false);
258                        el.set_enabled_state(true);
259                        el.check_ancestors_disabled_state_for_form_control();
260                    },
261                }
262                el.update_sequentially_focusable_status(can_gc);
263                self.validity_state(can_gc)
264                    .perform_validation_and_update(ValidationFlags::all(), can_gc);
265            },
266            local_name!("type") => match mutation {
267                AttributeMutation::Set(..) => {
268                    let value = match &**attr.value() {
269                        "reset" => ButtonType::Reset,
270                        "button" => ButtonType::Button,
271                        _ => ButtonType::Submit,
272                    };
273                    self.button_type.set(value);
274                    self.validity_state(can_gc)
275                        .perform_validation_and_update(ValidationFlags::all(), can_gc);
276                },
277                AttributeMutation::Removed => {
278                    self.button_type.set(ButtonType::Submit);
279                },
280            },
281            local_name!("form") => {
282                self.form_attribute_mutated(mutation, can_gc);
283                self.validity_state(can_gc)
284                    .perform_validation_and_update(ValidationFlags::empty(), can_gc);
285            },
286            _ => {},
287        }
288    }
289
290    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
291        if let Some(s) = self.super_type() {
292            s.bind_to_tree(context, can_gc);
293        }
294
295        self.upcast::<Element>()
296            .check_ancestors_disabled_state_for_form_control();
297    }
298
299    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
300        self.super_type().unwrap().unbind_from_tree(context, can_gc);
301
302        let node = self.upcast::<Node>();
303        let el = self.upcast::<Element>();
304        if node
305            .ancestors()
306            .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
307        {
308            el.check_ancestors_disabled_state_for_form_control();
309        } else {
310            el.check_disabled_attribute();
311        }
312    }
313}
314
315impl FormControl for HTMLButtonElement {
316    fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
317        self.form_owner.get()
318    }
319
320    fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
321        self.form_owner.set(form);
322    }
323
324    fn to_element(&self) -> &Element {
325        self.upcast::<Element>()
326    }
327}
328
329impl Validatable for HTMLButtonElement {
330    fn as_element(&self) -> &Element {
331        self.upcast()
332    }
333
334    fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
335        self.validity_state
336            .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
337    }
338
339    fn is_instance_validatable(&self) -> bool {
340        // https://html.spec.whatwg.org/multipage/#the-button-element%3Abarred-from-constraint-validation
341        // https://html.spec.whatwg.org/multipage/#enabling-and-disabling-form-controls%3A-the-disabled-attribute%3Abarred-from-constraint-validation
342        // https://html.spec.whatwg.org/multipage/#the-datalist-element%3Abarred-from-constraint-validation
343        self.button_type.get() == ButtonType::Submit &&
344            !self.upcast::<Element>().disabled_state() &&
345            !is_barred_by_datalist_ancestor(self.upcast())
346    }
347}
348
349impl Activatable for HTMLButtonElement {
350    fn as_element(&self) -> &Element {
351        self.upcast()
352    }
353
354    fn is_instance_activatable(&self) -> bool {
355        // https://html.spec.whatwg.org/multipage/#the-button-element
356        !self.upcast::<Element>().disabled_state()
357    }
358
359    /// <https://html.spec.whatwg.org/multipage/#the-button-element:activation-behaviour>
360    fn activation_behavior(&self, _event: &Event, target: &EventTarget, can_gc: CanGc) {
361        let ty = self.button_type.get();
362        match ty {
363            // https://html.spec.whatwg.org/multipage/#the-button-element:attr-button-type-submit-state
364            ButtonType::Submit => {
365                // Step 2. If element's node document is not fully active, then return.
366                if !target
367                    .downcast::<Node>()
368                    .is_none_or(|node| node.owner_document().is_fully_active())
369                {
370                    return;
371                }
372                if let Some(owner) = self.form_owner() {
373                    owner.submit(
374                        SubmittedFrom::NotFromForm,
375                        FormSubmitterElement::Button(self),
376                        can_gc,
377                    );
378                }
379            },
380            ButtonType::Reset => {
381                // Step 2. If element's node document is not fully active, then return.
382                if !target
383                    .downcast::<Node>()
384                    .is_none_or(|node| node.owner_document().is_fully_active())
385                {
386                    return;
387                }
388                if let Some(owner) = self.form_owner() {
389                    owner.reset(ResetFrom::NotFromForm, can_gc);
390                }
391            },
392            _ => (),
393        }
394    }
395}