script/dom/html/form_controls/
htmlbuttonelement.rs1use std::cell::Cell;
6use std::default::Default;
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, local_name};
10use js::context::{JSContext, NoGC};
11use js::rust::HandleObject;
12use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
13use script_bindings::codegen::GenericBindings::DocumentFragmentBinding::DocumentFragmentMethods;
14use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
15use style::selector_parser::PseudoElement;
16use stylo_dom::ElementState;
17
18use crate::dom::FlatTreeParent;
19use crate::dom::activation::Activatable;
20use crate::dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
21use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
22use crate::dom::bindings::inheritance::Castable;
23use crate::dom::bindings::root::{DomRoot, MutNullableDom};
24use crate::dom::bindings::str::DOMString;
25use crate::dom::commandevent::CommandEvent;
26use crate::dom::document::Document;
27use crate::dom::documentfragment::DocumentFragment;
28use crate::dom::element::attributes::storage::AttrRef;
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::htmlformelement::{
34 FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom,
35 SubmittedFrom,
36};
37use crate::dom::node::virtualmethods::{VirtualMethods, vtable_for};
38use crate::dom::node::{Node, NodeTraits};
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};
43
44#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
45enum ButtonType {
46 Submit,
47 Reset,
48 Button,
49}
50
51#[dom_struct]
52pub(crate) struct HTMLButtonElement {
53 htmlelement: HTMLElement,
54 button_type: Cell<ButtonType>,
55 form_owner: MutNullableDom<HTMLFormElement>,
56 labels_node_list: MutNullableDom<NodeList>,
57 validity_state: MutNullableDom<ValidityState>,
58}
59
60impl HTMLButtonElement {
61 fn new_inherited(
62 local_name: LocalName,
63 prefix: Option<Prefix>,
64 document: &Document,
65 ) -> HTMLButtonElement {
66 HTMLButtonElement {
67 htmlelement: HTMLElement::new_inherited_with_state(
68 ElementState::ENABLED,
69 local_name,
70 prefix,
71 document,
72 ),
73 button_type: Cell::new(ButtonType::Submit),
74 form_owner: Default::default(),
75 labels_node_list: Default::default(),
76 validity_state: Default::default(),
77 }
78 }
79
80 pub(crate) fn new(
81 cx: &mut js::context::JSContext,
82 local_name: LocalName,
83 prefix: Option<Prefix>,
84 document: &Document,
85 proto: Option<HandleObject>,
86 ) -> DomRoot<HTMLButtonElement> {
87 Node::reflect_node_with_proto(
88 cx,
89 Box::new(HTMLButtonElement::new_inherited(
90 local_name, prefix, document,
91 )),
92 document,
93 proto,
94 )
95 }
96
97 #[inline]
98 pub(crate) fn is_submit_button(&self) -> bool {
99 self.button_type.get() == ButtonType::Submit
100 }
101}
102
103impl HTMLButtonElementMethods<crate::DomTypeHolder> for HTMLButtonElement {
104 fn Command(&self) -> DOMString {
106 match self.command_state() {
108 CommandState::Custom => self
110 .upcast::<Element>()
111 .get_string_attribute(&local_name!("command")),
112 CommandState::Unknown => DOMString::default(),
114 CommandState::Close => DOMString::from_static("close"),
116 CommandState::ShowModal => DOMString::from_static("show-modal"),
117 }
118 }
119
120 make_setter!(SetCommand, "command");
122
123 make_bool_getter!(Disabled, "disabled");
125
126 make_bool_setter!(SetDisabled, "disabled");
128
129 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
131 self.form_owner()
132 }
133
134 fn Type(&self) -> DOMString {
136 match self.button_type.get() {
137 ButtonType::Submit => DOMString::from_static("submit"),
138 ButtonType::Button => DOMString::from_static("button"),
139 ButtonType::Reset => DOMString::from_static("reset"),
140 }
141 }
142
143 make_setter!(SetType, "type");
145
146 make_form_action_getter!(FormAction, "formaction");
148
149 make_setter!(SetFormAction, "formaction");
151
152 make_enumerated_getter!(
154 FormEnctype,
155 "formenctype",
156 "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain",
157 invalid => "application/x-www-form-urlencoded"
158 );
159
160 make_setter!(SetFormEnctype, "formenctype");
162
163 make_enumerated_getter!(
165 FormMethod,
166 "formmethod",
167 "get" | "post" | "dialog",
168 invalid => "get"
169 );
170
171 make_setter!(SetFormMethod, "formmethod");
173
174 make_getter!(FormTarget, "formtarget");
176
177 make_setter!(SetFormTarget, "formtarget");
179
180 make_bool_getter!(FormNoValidate, "formnovalidate");
182
183 make_bool_setter!(SetFormNoValidate, "formnovalidate");
185
186 make_getter!(Name, "name");
188
189 make_atomic_setter!(SetName, "name");
191
192 make_getter!(Value, "value");
194
195 make_setter!(SetValue, "value");
197
198 make_labels_getter!(Labels, labels_node_list);
200
201 fn WillValidate(&self, no_gc: &NoGC) -> bool {
203 self.is_instance_validatable(no_gc)
204 }
205
206 fn Validity(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
208 self.validity_state(cx)
209 }
210
211 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
213 self.check_validity(cx)
214 }
215
216 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
218 self.report_validity(cx)
219 }
220
221 fn ValidationMessage(&self, cx: &mut JSContext) -> DOMString {
223 self.validation_message(cx)
224 }
225
226 fn SetCustomValidity(&self, cx: &mut JSContext, error: DOMString) {
228 self.validity_state(cx).set_custom_error_message(cx, error);
229 }
230}
231
232impl HTMLButtonElement {
233 pub(crate) fn form_datum(&self, submitter: Option<FormSubmitterElement>) -> Option<FormDatum> {
236 if let Some(FormSubmitterElement::Button(submitter)) = submitter {
240 if submitter != self {
241 return None;
242 }
243 } else {
244 return None;
245 }
246 let ty = self.Type();
248 let name = self.Name();
250
251 if name.is_empty() {
252 return None;
254 }
255
256 Some(FormDatum {
258 ty,
259 name,
260 value: FormDatumValue::String(self.Value()),
261 })
262 }
263
264 fn set_type(&self, cx: &mut JSContext, value: DOMString) {
265 let value = if value.eq_ignore_ascii_case("reset") {
266 ButtonType::Reset
267 } else if value.eq_ignore_ascii_case("button") {
268 ButtonType::Button
269 } else if value.eq_ignore_ascii_case("submit") {
270 ButtonType::Submit
271 } else {
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 self.button_type.set(value);
282 self.validity_state(cx)
283 .perform_validation_and_update(cx, ValidationFlags::all());
284 }
285
286 fn command_for_element(&self, cx: &mut JSContext) -> Option<DomRoot<Element>> {
287 let command_for_value = self
288 .upcast::<Element>()
289 .get_attribute_string_value(&local_name!("commandfor"))?
290 .into();
291
292 let root_node = self
293 .upcast::<Node>()
294 .GetRootNode(&GetRootNodeOptions::empty());
295
296 if let Some(document) = root_node.downcast::<Document>() {
297 return document.GetElementById(cx, command_for_value);
298 } else if let Some(document_fragment) = root_node.downcast::<DocumentFragment>() {
299 return document_fragment.GetElementById(cx, command_for_value);
300 }
301 unreachable!("Button element must be in a document or document fragment");
302 }
303
304 fn command_state(&self) -> CommandState {
305 let command = self
306 .upcast::<Element>()
307 .get_string_attribute(&local_name!("command"));
308 if command.starts_with_str("--") {
309 return CommandState::Custom;
310 }
311 if command.eq_ignore_ascii_case("close") {
312 return CommandState::Close;
313 }
314 if command.eq_ignore_ascii_case("show-modal") {
315 return CommandState::ShowModal;
316 }
317
318 CommandState::Unknown
319 }
320
321 fn determine_if_command_is_valid_for_target(command: CommandState, target: &Element) -> bool {
323 if command == CommandState::Unknown {
325 return false;
326 }
327 if command == CommandState::Custom {
329 return true;
330 }
331 if !target.is_html_element() {
333 return false;
334 }
335 vtable_for(target.upcast::<Node>()).is_valid_command_steps(command)
343 }
344
345 pub(crate) fn optional_value(&self) -> Option<DOMString> {
347 self.upcast::<Element>()
350 .get_attribute_string_value(&local_name!("value"))
351 .map(|value| value.into())
352 }
353}
354
355impl VirtualMethods for HTMLButtonElement {
356 fn super_type(&self) -> Option<&dyn VirtualMethods> {
357 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
358 }
359
360 fn attribute_mutated(
361 &self,
362 cx: &mut js::context::JSContext,
363 attr: AttrRef<'_>,
364 mutation: AttributeMutation,
365 ) {
366 self.super_type()
367 .unwrap()
368 .attribute_mutated(cx, attr, mutation);
369 match *attr.local_name() {
370 local_name!("disabled") => {
371 self.validity_state(cx)
372 .perform_validation_and_update(cx, ValidationFlags::all());
373 },
374 local_name!("type") => self.set_type(cx, attr.to_dom_string()),
375 local_name!("command") => self.set_type(
376 cx,
377 self.upcast::<Element>()
378 .get_string_attribute(&local_name!("type")),
379 ),
380 local_name!("commandfor") => self.set_type(
381 cx,
382 self.upcast::<Element>()
383 .get_string_attribute(&local_name!("type")),
384 ),
385 local_name!("form") => {
386 self.form_attribute_mutated(cx, mutation);
387 self.validity_state(cx)
388 .perform_validation_and_update(cx, ValidationFlags::empty());
389 },
390 _ => {},
391 }
392 }
393}
394
395impl FormControl for HTMLButtonElement {
396 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
397 self.form_owner.get()
398 }
399
400 fn set_form_owner(&self, _cx: &mut JSContext, form: Option<&HTMLFormElement>) {
401 self.form_owner.set(form);
402 }
403
404 fn to_html_element(&self) -> &HTMLElement {
405 self.upcast::<HTMLElement>()
406 }
407}
408
409impl Validatable for HTMLButtonElement {
410 fn as_element(&self) -> &Element {
411 self.upcast()
412 }
413
414 fn validity_state(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
415 self.validity_state
416 .or_init(|| ValidityState::new(cx, &self.owner_window(), self.upcast()))
417 }
418
419 fn is_instance_validatable(&self, no_gc: &NoGC) -> bool {
420 self.button_type.get() == ButtonType::Submit &&
424 !self.upcast::<Element>().disabled_state() &&
425 !is_barred_by_datalist_ancestor(no_gc, self.upcast())
426 }
427}
428
429impl Activatable for HTMLButtonElement {
430 fn as_element(&self) -> &Element {
431 self.upcast()
432 }
433
434 fn is_instance_activatable(&self) -> bool {
435 !self.upcast::<Element>().disabled_state()
437 }
438
439 fn activation_behavior(
441 &self,
442 cx: &mut js::context::JSContext,
443 event: &Event,
444 target: &EventTarget,
445 ) {
446 if !target
448 .downcast::<Node>()
449 .is_none_or(|node| node.owner_document().is_fully_active())
450 {
451 return;
452 }
453
454 let button_type = self.button_type.get();
455 if let Some(owner) = self.form_owner() {
457 if button_type == ButtonType::Submit {
460 owner.submit(
461 cx,
462 SubmittedFrom::NotFromForm,
463 FormSubmitterElement::Button(self),
464 );
465 return;
466 }
467 if button_type == ButtonType::Reset {
470 owner.reset(cx, ResetFrom::NotFromForm);
471 return;
472 }
473 if button_type == ButtonType::Button &&
475 !self
476 .upcast::<Element>()
477 .get_string_attribute(&local_name!("type"))
478 .eq_ignore_ascii_case("button")
479 {
480 return;
481 }
482 }
483 if let Some(pseudo_element) = self.upcast::<Node>().implemented_pseudo_element() {
485 if pseudo_element == PseudoElement::FileSelectorButton {
486 let FlatTreeParent::Parent(parent) =
487 self.upcast::<Node>().parent_in_flat_tree(cx.no_gc())
488 else {
489 return;
490 };
491
492 parent
493 .as_rooted()
494 .downcast::<HTMLInputElement>()
495 .expect("File select button should always be a child of an input element")
496 .activation_behavior(cx, event, target);
497 }
498
499 return;
500 }
501
502 if let Some(target) = self.command_for_element(cx) {
506 let command = self.command_state();
508 if !Self::determine_if_command_is_valid_for_target(command, &target) {
510 return;
511 }
512 let event = CommandEvent::new(
518 cx,
519 &self.owner_window(),
520 atom!("command"),
521 EventBubbles::DoesNotBubble,
522 EventCancelable::Cancelable,
523 Some(self.upcast()),
524 self.upcast::<Element>()
525 .get_string_attribute(&local_name!("command")),
526 );
527 let event = event.upcast::<Event>();
528 if !event.fire(cx, target.upcast::<EventTarget>()) {
529 return;
530 }
531 let target_node = target.upcast::<Node>();
533 if !target_node.is_connected() {
534 return;
535 }
536 if command == CommandState::Custom {
538 return;
539 }
540 let _ = vtable_for(target_node).command_steps(cx, DomRoot::from_ref(self), command);
544 }
545 }
548}
549
550#[derive(Copy, Clone, Eq, PartialEq, Debug)]
551pub(crate) enum CommandState {
552 Unknown,
553 Custom,
554 ShowModal,
555 Close,
556}