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