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!(cx, SetCommand, "command");
124
125 make_bool_getter!(Disabled, "disabled");
127
128 make_bool_setter!(cx, 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!(cx, SetType, "type");
147
148 make_form_action_getter!(FormAction, "formaction");
150
151 make_setter!(cx, 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!(cx, SetFormEnctype, "formenctype");
164
165 make_enumerated_getter!(
167 FormMethod,
168 "formmethod",
169 "get" | "post" | "dialog",
170 invalid => "get"
171 );
172
173 make_setter!(cx, SetFormMethod, "formmethod");
175
176 make_getter!(FormTarget, "formtarget");
178
179 make_setter!(cx, SetFormTarget, "formtarget");
181
182 make_bool_getter!(FormNoValidate, "formnovalidate");
184
185 make_bool_setter!(cx, SetFormNoValidate, "formnovalidate");
187
188 make_getter!(Name, "name");
190
191 make_atomic_setter!(cx, SetName, "name");
193
194 make_getter!(Value, "value");
196
197 make_setter!(cx, 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, cx: &mut JSContext, error: DOMString) {
230 self.validity_state(CanGc::from_cx(cx))
231 .set_custom_error_message(error);
232 }
233}
234
235impl HTMLButtonElement {
236 pub(crate) fn form_datum(&self, submitter: Option<FormSubmitterElement>) -> Option<FormDatum> {
239 if let Some(FormSubmitterElement::Button(submitter)) = submitter {
243 if submitter != self {
244 return None;
245 }
246 } else {
247 return None;
248 }
249 let ty = self.Type();
251 let name = self.Name();
253
254 if name.is_empty() {
255 return None;
257 }
258
259 Some(FormDatum {
261 ty,
262 name,
263 value: FormDatumValue::String(self.Value()),
264 })
265 }
266
267 fn set_type(&self, value: DOMString, can_gc: CanGc) {
268 let value = match value.to_ascii_lowercase().as_str() {
269 "reset" => ButtonType::Reset,
270 "button" => ButtonType::Button,
271 "submit" => ButtonType::Submit,
272 _ => {
273 let element = self.upcast::<Element>();
274 if element.has_attribute(&local_name!("command")) ||
275 element.has_attribute(&local_name!("commandfor"))
276 {
277 ButtonType::Button
278 } else {
279 ButtonType::Submit
280 }
281 },
282 };
283 self.button_type.set(value);
284 self.validity_state(can_gc)
285 .perform_validation_and_update(ValidationFlags::all(), can_gc);
286 }
287
288 fn command_for_element(&self) -> Option<DomRoot<Element>> {
289 let command_for_value = self
290 .upcast::<Element>()
291 .get_attribute(&local_name!("commandfor"))?
292 .Value();
293
294 let root_node = self
295 .upcast::<Node>()
296 .GetRootNode(&GetRootNodeOptions::empty());
297
298 if let Some(document) = root_node.downcast::<Document>() {
299 return document.GetElementById(command_for_value);
300 } else if let Some(document_fragment) = root_node.downcast::<DocumentFragment>() {
301 return document_fragment.GetElementById(command_for_value);
302 }
303 unreachable!("Button element must be in a document or document fragment");
304 }
305
306 fn command_state(&self) -> CommandState {
307 let command = self
308 .upcast::<Element>()
309 .get_string_attribute(&local_name!("command"));
310 if command.starts_with_str("--") {
311 return CommandState::Custom;
312 }
313 let value = command.to_ascii_lowercase();
314 if value == "close" {
315 return CommandState::Close;
316 }
317 if value == "show-modal" {
318 return CommandState::ShowModal;
319 }
320
321 CommandState::Unknown
322 }
323
324 fn determine_if_command_is_valid_for_target(
326 command: CommandState,
327 target: DomRoot<Element>,
328 ) -> bool {
329 if command == CommandState::Unknown {
331 return false;
332 }
333 if command == CommandState::Custom {
335 return true;
336 }
337 if !target.is_html_element() {
339 return false;
340 }
341 vtable_for(target.upcast::<Node>()).is_valid_command_steps(command)
349 }
350
351 pub(crate) fn optional_value(&self) -> Option<DOMString> {
353 self.upcast::<Element>()
356 .get_attribute(&local_name!("value"))
357 .map(|attribute| attribute.Value())
358 }
359}
360
361impl VirtualMethods for HTMLButtonElement {
362 fn super_type(&self) -> Option<&dyn VirtualMethods> {
363 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
364 }
365
366 fn attribute_mutated(
367 &self,
368 cx: &mut js::context::JSContext,
369 attr: &Attr,
370 mutation: AttributeMutation,
371 ) {
372 self.super_type()
373 .unwrap()
374 .attribute_mutated(cx, attr, mutation);
375 match *attr.local_name() {
376 local_name!("disabled") => {
377 let el = self.upcast::<Element>();
378 match mutation {
379 AttributeMutation::Set(Some(_), _) => {},
380 AttributeMutation::Set(None, _) => {
381 el.set_disabled_state(true);
382 el.set_enabled_state(false);
383 },
384 AttributeMutation::Removed => {
385 el.set_disabled_state(false);
386 el.set_enabled_state(true);
387 el.check_ancestors_disabled_state_for_form_control();
388 },
389 }
390 self.validity_state(CanGc::from_cx(cx))
391 .perform_validation_and_update(ValidationFlags::all(), CanGc::from_cx(cx));
392 },
393 local_name!("type") => self.set_type(attr.Value(), CanGc::from_cx(cx)),
394 local_name!("command") => self.set_type(
395 self.upcast::<Element>()
396 .get_string_attribute(&local_name!("type")),
397 CanGc::from_cx(cx),
398 ),
399 local_name!("commandfor") => self.set_type(
400 self.upcast::<Element>()
401 .get_string_attribute(&local_name!("type")),
402 CanGc::from_cx(cx),
403 ),
404 local_name!("form") => {
405 self.form_attribute_mutated(mutation, CanGc::from_cx(cx));
406 self.validity_state(CanGc::from_cx(cx))
407 .perform_validation_and_update(ValidationFlags::empty(), CanGc::from_cx(cx));
408 },
409 _ => {},
410 }
411 }
412
413 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
414 if let Some(s) = self.super_type() {
415 s.bind_to_tree(cx, context);
416 }
417
418 self.upcast::<Element>()
419 .check_ancestors_disabled_state_for_form_control();
420 }
421
422 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
423 self.super_type().unwrap().unbind_from_tree(cx, context);
424
425 let node = self.upcast::<Node>();
426 let el = self.upcast::<Element>();
427 if node
428 .ancestors()
429 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
430 {
431 el.check_ancestors_disabled_state_for_form_control();
432 } else {
433 el.check_disabled_attribute();
434 }
435 }
436}
437
438impl FormControl for HTMLButtonElement {
439 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
440 self.form_owner.get()
441 }
442
443 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
444 self.form_owner.set(form);
445 }
446
447 fn to_element(&self) -> &Element {
448 self.upcast::<Element>()
449 }
450}
451
452impl Validatable for HTMLButtonElement {
453 fn as_element(&self) -> &Element {
454 self.upcast()
455 }
456
457 fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
458 self.validity_state
459 .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
460 }
461
462 fn is_instance_validatable(&self) -> bool {
463 self.button_type.get() == ButtonType::Submit &&
467 !self.upcast::<Element>().disabled_state() &&
468 !is_barred_by_datalist_ancestor(self.upcast())
469 }
470}
471
472impl Activatable for HTMLButtonElement {
473 fn as_element(&self) -> &Element {
474 self.upcast()
475 }
476
477 fn is_instance_activatable(&self) -> bool {
478 !self.upcast::<Element>().disabled_state()
480 }
481
482 fn activation_behavior(
484 &self,
485 cx: &mut js::context::JSContext,
486 event: &Event,
487 target: &EventTarget,
488 ) {
489 if !target
491 .downcast::<Node>()
492 .is_none_or(|node| node.owner_document().is_fully_active())
493 {
494 return;
495 }
496
497 let button_type = self.button_type.get();
498 if let Some(owner) = self.form_owner() {
500 if button_type == ButtonType::Submit {
503 owner.submit(
504 cx,
505 SubmittedFrom::NotFromForm,
506 FormSubmitterElement::Button(self),
507 );
508 return;
509 }
510 if button_type == ButtonType::Reset {
513 owner.reset(cx, ResetFrom::NotFromForm);
514 return;
515 }
516 if button_type == ButtonType::Button &&
518 self.upcast::<Element>()
519 .get_string_attribute(&local_name!("type"))
520 .to_ascii_lowercase() !=
521 "button"
522 {
523 return;
524 }
525 }
526 if let Some(pseudo_element) = self.upcast::<Node>().implemented_pseudo_element() {
528 if pseudo_element == PseudoElement::FileSelectorButton {
529 let Some(parent) = self.upcast::<Node>().parent_in_flat_tree() else {
530 return;
531 };
532
533 parent
534 .downcast::<HTMLInputElement>()
535 .expect("File select button should always be a child of an input element")
536 .activation_behavior(cx, event, target);
537 }
538
539 return;
540 }
541
542 if let Some(target) = self.command_for_element() {
546 let command = self.command_state();
548 if !Self::determine_if_command_is_valid_for_target(command, target.clone()) {
550 return;
551 }
552 let event = CommandEvent::new(
558 &self.owner_window(),
559 atom!("command"),
560 EventBubbles::DoesNotBubble,
561 EventCancelable::Cancelable,
562 Some(DomRoot::from_ref(self.upcast())),
563 self.upcast::<Element>()
564 .get_string_attribute(&local_name!("command")),
565 CanGc::from_cx(cx),
566 );
567 let event = event.upcast::<Event>();
568 if !event.fire(target.upcast::<EventTarget>(), CanGc::from_cx(cx)) {
569 return;
570 }
571 let target_node = target.upcast::<Node>();
573 if !target_node.is_connected() {
574 return;
575 }
576 if command == CommandState::Custom {
578 return;
579 }
580 let _ = vtable_for(target_node).command_steps(cx, DomRoot::from_ref(self), command);
584 }
585 }
588}
589
590#[derive(Copy, Clone, Eq, PartialEq, Debug)]
591pub(crate) enum CommandState {
592 Unknown,
593 Custom,
594 ShowModal,
595 Close,
596}