1use std::cell::Cell;
6use std::default::Default;
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, local_name};
10use js::context::JSContext;
11use js::rust::HandleObject;
12use script_bindings::codegen::GenericBindings::AttrBinding::AttrMethods;
13use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
14use script_bindings::codegen::GenericBindings::DocumentFragmentBinding::DocumentFragmentMethods;
15use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
16use style::selector_parser::PseudoElement;
17use stylo_dom::ElementState;
18
19use crate::dom::activation::Activatable;
20use crate::dom::attr::Attr;
21use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
22use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::root::{DomRoot, MutNullableDom};
25use crate::dom::bindings::str::DOMString;
26use crate::dom::commandevent::CommandEvent;
27use crate::dom::document::Document;
28use crate::dom::documentfragment::DocumentFragment;
29use crate::dom::element::{AttributeMutation, Element};
30use crate::dom::event::{Event, EventBubbles, EventCancelable};
31use crate::dom::eventtarget::EventTarget;
32use crate::dom::html::htmlelement::HTMLElement;
33use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
34use crate::dom::html::htmlformelement::{
35 FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom,
36 SubmittedFrom,
37};
38use crate::dom::node::{BindContext, Node, NodeTraits, UnbindContext};
39use crate::dom::nodelist::NodeList;
40use crate::dom::types::HTMLInputElement;
41use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
42use crate::dom::validitystate::{ValidationFlags, ValidityState};
43use crate::dom::virtualmethods::{VirtualMethods, vtable_for};
44use crate::script_runtime::CanGc;
45
46#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
47enum ButtonType {
48 Submit,
49 Reset,
50 Button,
51}
52
53#[dom_struct]
54pub(crate) struct HTMLButtonElement {
55 htmlelement: HTMLElement,
56 button_type: Cell<ButtonType>,
57 form_owner: MutNullableDom<HTMLFormElement>,
58 labels_node_list: MutNullableDom<NodeList>,
59 validity_state: MutNullableDom<ValidityState>,
60}
61
62impl HTMLButtonElement {
63 fn new_inherited(
64 local_name: LocalName,
65 prefix: Option<Prefix>,
66 document: &Document,
67 ) -> HTMLButtonElement {
68 HTMLButtonElement {
69 htmlelement: HTMLElement::new_inherited_with_state(
70 ElementState::ENABLED,
71 local_name,
72 prefix,
73 document,
74 ),
75 button_type: Cell::new(ButtonType::Submit),
76 form_owner: Default::default(),
77 labels_node_list: Default::default(),
78 validity_state: Default::default(),
79 }
80 }
81
82 pub(crate) fn new(
83 cx: &mut js::context::JSContext,
84 local_name: LocalName,
85 prefix: Option<Prefix>,
86 document: &Document,
87 proto: Option<HandleObject>,
88 ) -> DomRoot<HTMLButtonElement> {
89 Node::reflect_node_with_proto(
90 cx,
91 Box::new(HTMLButtonElement::new_inherited(
92 local_name, prefix, document,
93 )),
94 document,
95 proto,
96 )
97 }
98
99 #[inline]
100 pub(crate) fn is_submit_button(&self) -> bool {
101 self.button_type.get() == ButtonType::Submit
102 }
103}
104
105impl HTMLButtonElementMethods<crate::DomTypeHolder> for HTMLButtonElement {
106 fn Command(&self) -> DOMString {
108 match self.command_state() {
110 CommandState::Custom => self
112 .upcast::<Element>()
113 .get_string_attribute(&local_name!("command")),
114 CommandState::Unknown => DOMString::default(),
116 CommandState::Close => DOMString::from("close"),
118 CommandState::ShowModal => DOMString::from("show-modal"),
119 }
120 }
121
122 make_setter!(SetCommand, "command");
124
125 make_bool_getter!(Disabled, "disabled");
127
128 make_bool_setter!(SetDisabled, "disabled");
130
131 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
133 self.form_owner()
134 }
135
136 fn Type(&self) -> DOMString {
138 match self.button_type.get() {
139 ButtonType::Submit => DOMString::from("submit"),
140 ButtonType::Button => DOMString::from("button"),
141 ButtonType::Reset => DOMString::from("reset"),
142 }
143 }
144
145 make_setter!(SetType, "type");
147
148 make_form_action_getter!(FormAction, "formaction");
150
151 make_setter!(SetFormAction, "formaction");
153
154 make_enumerated_getter!(
156 FormEnctype,
157 "formenctype",
158 "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain",
159 invalid => "application/x-www-form-urlencoded"
160 );
161
162 make_setter!(SetFormEnctype, "formenctype");
164
165 make_enumerated_getter!(
167 FormMethod,
168 "formmethod",
169 "get" | "post" | "dialog",
170 invalid => "get"
171 );
172
173 make_setter!(SetFormMethod, "formmethod");
175
176 make_getter!(FormTarget, "formtarget");
178
179 make_setter!(SetFormTarget, "formtarget");
181
182 make_bool_getter!(FormNoValidate, "formnovalidate");
184
185 make_bool_setter!(SetFormNoValidate, "formnovalidate");
187
188 make_getter!(Name, "name");
190
191 make_atomic_setter!(SetName, "name");
193
194 make_getter!(Value, "value");
196
197 make_setter!(SetValue, "value");
199
200 make_labels_getter!(Labels, labels_node_list);
202
203 fn WillValidate(&self) -> bool {
205 self.is_instance_validatable()
206 }
207
208 fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
210 self.validity_state(can_gc)
211 }
212
213 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
215 self.check_validity(cx)
216 }
217
218 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
220 self.report_validity(cx)
221 }
222
223 fn ValidationMessage(&self) -> DOMString {
225 self.validation_message()
226 }
227
228 fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
230 self.validity_state(can_gc).set_custom_error_message(error);
231 }
232}
233
234impl HTMLButtonElement {
235 pub(crate) fn form_datum(&self, submitter: Option<FormSubmitterElement>) -> Option<FormDatum> {
238 if let Some(FormSubmitterElement::Button(submitter)) = submitter {
242 if submitter != self {
243 return None;
244 }
245 } else {
246 return None;
247 }
248 let ty = self.Type();
250 let name = self.Name();
252
253 if name.is_empty() {
254 return None;
256 }
257
258 Some(FormDatum {
260 ty,
261 name,
262 value: FormDatumValue::String(self.Value()),
263 })
264 }
265
266 fn set_type(&self, value: DOMString, can_gc: CanGc) {
267 let value = match value.to_ascii_lowercase().as_str() {
268 "reset" => ButtonType::Reset,
269 "button" => ButtonType::Button,
270 "submit" => ButtonType::Submit,
271 _ => {
272 let element = self.upcast::<Element>();
273 if element.has_attribute(&local_name!("command")) ||
274 element.has_attribute(&local_name!("commandfor"))
275 {
276 ButtonType::Button
277 } else {
278 ButtonType::Submit
279 }
280 },
281 };
282 self.button_type.set(value);
283 self.validity_state(can_gc)
284 .perform_validation_and_update(ValidationFlags::all(), can_gc);
285 }
286
287 fn command_for_element(&self) -> Option<DomRoot<Element>> {
288 let command_for_value = self
289 .upcast::<Element>()
290 .get_attribute(&local_name!("commandfor"))?
291 .Value();
292
293 let root_node = self
294 .upcast::<Node>()
295 .GetRootNode(&GetRootNodeOptions::empty());
296
297 if let Some(document) = root_node.downcast::<Document>() {
298 return document.GetElementById(command_for_value);
299 } else if let Some(document_fragment) = root_node.downcast::<DocumentFragment>() {
300 return document_fragment.GetElementById(command_for_value);
301 }
302 unreachable!("Button element must be in a document or document fragment");
303 }
304
305 fn command_state(&self) -> CommandState {
306 let command = self
307 .upcast::<Element>()
308 .get_string_attribute(&local_name!("command"));
309 if command.starts_with_str("--") {
310 return CommandState::Custom;
311 }
312 let value = command.to_ascii_lowercase();
313 if value == "close" {
314 return CommandState::Close;
315 }
316 if value == "show-modal" {
317 return CommandState::ShowModal;
318 }
319
320 CommandState::Unknown
321 }
322
323 fn determine_if_command_is_valid_for_target(
325 command: CommandState,
326 target: DomRoot<Element>,
327 ) -> bool {
328 if command == CommandState::Unknown {
330 return false;
331 }
332 if command == CommandState::Custom {
334 return true;
335 }
336 if !target.is_html_element() {
338 return false;
339 }
340 vtable_for(target.upcast::<Node>()).is_valid_command_steps(command)
348 }
349
350 pub(crate) fn optional_value(&self) -> Option<DOMString> {
352 self.upcast::<Element>()
355 .get_attribute(&local_name!("value"))
356 .map(|attribute| attribute.Value())
357 }
358}
359
360impl VirtualMethods for HTMLButtonElement {
361 fn super_type(&self) -> Option<&dyn VirtualMethods> {
362 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
363 }
364
365 fn attribute_mutated(
366 &self,
367 cx: &mut js::context::JSContext,
368 attr: &Attr,
369 mutation: AttributeMutation,
370 ) {
371 self.super_type()
372 .unwrap()
373 .attribute_mutated(cx, attr, mutation);
374 match *attr.local_name() {
375 local_name!("disabled") => {
376 let el = self.upcast::<Element>();
377 match mutation {
378 AttributeMutation::Set(Some(_), _) => {},
379 AttributeMutation::Set(None, _) => {
380 el.set_disabled_state(true);
381 el.set_enabled_state(false);
382 },
383 AttributeMutation::Removed => {
384 el.set_disabled_state(false);
385 el.set_enabled_state(true);
386 el.check_ancestors_disabled_state_for_form_control();
387 },
388 }
389 self.validity_state(CanGc::from_cx(cx))
390 .perform_validation_and_update(ValidationFlags::all(), CanGc::from_cx(cx));
391 },
392 local_name!("type") => self.set_type(attr.Value(), CanGc::from_cx(cx)),
393 local_name!("command") => self.set_type(
394 self.upcast::<Element>()
395 .get_string_attribute(&local_name!("type")),
396 CanGc::from_cx(cx),
397 ),
398 local_name!("commandfor") => self.set_type(
399 self.upcast::<Element>()
400 .get_string_attribute(&local_name!("type")),
401 CanGc::from_cx(cx),
402 ),
403 local_name!("form") => {
404 self.form_attribute_mutated(mutation, CanGc::from_cx(cx));
405 self.validity_state(CanGc::from_cx(cx))
406 .perform_validation_and_update(ValidationFlags::empty(), CanGc::from_cx(cx));
407 },
408 _ => {},
409 }
410 }
411
412 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
413 if let Some(s) = self.super_type() {
414 s.bind_to_tree(cx, context);
415 }
416
417 self.upcast::<Element>()
418 .check_ancestors_disabled_state_for_form_control();
419 }
420
421 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
422 self.super_type().unwrap().unbind_from_tree(context, can_gc);
423
424 let node = self.upcast::<Node>();
425 let el = self.upcast::<Element>();
426 if node
427 .ancestors()
428 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
429 {
430 el.check_ancestors_disabled_state_for_form_control();
431 } else {
432 el.check_disabled_attribute();
433 }
434 }
435}
436
437impl FormControl for HTMLButtonElement {
438 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
439 self.form_owner.get()
440 }
441
442 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
443 self.form_owner.set(form);
444 }
445
446 fn to_element(&self) -> &Element {
447 self.upcast::<Element>()
448 }
449}
450
451impl Validatable for HTMLButtonElement {
452 fn as_element(&self) -> &Element {
453 self.upcast()
454 }
455
456 fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
457 self.validity_state
458 .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
459 }
460
461 fn is_instance_validatable(&self) -> bool {
462 self.button_type.get() == ButtonType::Submit &&
466 !self.upcast::<Element>().disabled_state() &&
467 !is_barred_by_datalist_ancestor(self.upcast())
468 }
469}
470
471impl Activatable for HTMLButtonElement {
472 fn as_element(&self) -> &Element {
473 self.upcast()
474 }
475
476 fn is_instance_activatable(&self) -> bool {
477 !self.upcast::<Element>().disabled_state()
479 }
480
481 fn activation_behavior(&self, event: &Event, target: &EventTarget, can_gc: CanGc) {
483 if !target
485 .downcast::<Node>()
486 .is_none_or(|node| node.owner_document().is_fully_active())
487 {
488 return;
489 }
490
491 let button_type = self.button_type.get();
492 if let Some(owner) = self.form_owner() {
494 if button_type == ButtonType::Submit {
497 owner.submit(
498 SubmittedFrom::NotFromForm,
499 FormSubmitterElement::Button(self),
500 can_gc,
501 );
502 return;
503 }
504 if button_type == ButtonType::Reset {
507 owner.reset(ResetFrom::NotFromForm, can_gc);
508 return;
509 }
510 if button_type == ButtonType::Button &&
512 self.upcast::<Element>()
513 .get_string_attribute(&local_name!("type"))
514 .to_ascii_lowercase() !=
515 "button"
516 {
517 return;
518 }
519 }
520 if let Some(pseudo_element) = self.upcast::<Node>().implemented_pseudo_element() {
522 if pseudo_element == PseudoElement::FileSelectorButton {
523 let Some(parent) = self.upcast::<Node>().parent_in_flat_tree() else {
524 return;
525 };
526
527 parent
528 .downcast::<HTMLInputElement>()
529 .expect("File select button should always be a child of an input element")
530 .activation_behavior(event, target, can_gc);
531 }
532
533 return;
534 }
535
536 if let Some(target) = self.command_for_element() {
540 let command = self.command_state();
542 if !Self::determine_if_command_is_valid_for_target(command, target.clone()) {
544 return;
545 }
546 let event = CommandEvent::new(
552 &self.owner_window(),
553 atom!("command"),
554 EventBubbles::DoesNotBubble,
555 EventCancelable::Cancelable,
556 Some(DomRoot::from_ref(self.upcast())),
557 self.upcast::<Element>()
558 .get_string_attribute(&local_name!("command")),
559 can_gc,
560 );
561 let event = event.upcast::<Event>();
562 if !event.fire(target.upcast::<EventTarget>(), can_gc) {
563 return;
564 }
565 let target_node = target.upcast::<Node>();
567 if !target_node.is_connected() {
568 return;
569 }
570 if command == CommandState::Custom {
572 return;
573 }
574 let _ = vtable_for(target_node).command_steps(DomRoot::from_ref(self), command, can_gc);
578 }
579 }
582}
583
584#[derive(Copy, Clone, Eq, PartialEq, Debug)]
585pub(crate) enum CommandState {
586 Unknown,
587 Custom,
588 ShowModal,
589 Close,
590}