1use std::cell::{Cell, RefCell};
6use std::cmp::Ordering;
7use std::ops::Range;
8use std::path::PathBuf;
9use std::ptr::NonNull;
10use std::str::FromStr;
11use std::{f64, ptr};
12
13use base::generic_channel::GenericSender;
14use base::text::{Utf8CodeUnitLength, Utf16CodeUnitLength};
15use base::{Lines, RopeMovement};
16use dom_struct::dom_struct;
17use embedder_traits::{
18 EmbedderControlRequest, FilePickerRequest, FilterPattern, InputMethodRequest, InputMethodType,
19 RgbColor, SelectedFile,
20};
21use encoding_rs::Encoding;
22use html5ever::{LocalName, Prefix, QualName, local_name, ns};
23use itertools::Itertools;
24use js::jsapi::{
25 ClippedTime, DateGetMsecSinceEpoch, Handle, JS_ClearPendingException, JSObject, NewDateObject,
26 NewUCRegExpObject, ObjectIsDate, RegExpFlag_UnicodeSets, RegExpFlags,
27};
28use js::jsval::UndefinedValue;
29use js::rust::wrappers::{CheckRegExpSyntax, ExecuteRegExpNoStatics, ObjectIsRegExp};
30use js::rust::{HandleObject, MutableHandleObject};
31use net_traits::blob_url_store::get_blob_origin;
32use script_bindings::codegen::GenericBindings::AttrBinding::AttrMethods;
33use script_bindings::codegen::GenericBindings::CharacterDataBinding::CharacterDataMethods;
34use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
35use script_bindings::domstring::parse_floating_point_number;
36use style::attr::AttrValue;
37use style::selector_parser::PseudoElement;
38use style::str::split_commas;
39use stylo_atoms::Atom;
40use stylo_dom::ElementState;
41use time::{Month, OffsetDateTime, Time};
42use unicode_bidi::{BidiClass, bidi_class};
43use url::Url;
44use webdriver::error::ErrorStatus;
45
46use crate::clipboard_provider::EmbedderClipboardProvider;
47use crate::dom::activation::Activatable;
48use crate::dom::attr::Attr;
49use crate::dom::bindings::cell::{DomRefCell, Ref};
50use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
51use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
52use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
53use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
54use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
55use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
56use crate::dom::bindings::error::{Error, ErrorResult};
57use crate::dom::bindings::inheritance::Castable;
58use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
59use crate::dom::bindings::str::{DOMString, FromInputValueString, ToInputValueString, USVString};
60use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType};
61use crate::dom::compositionevent::CompositionEvent;
62use crate::dom::document::Document;
63use crate::dom::document_embedder_controls::ControlElement;
64use crate::dom::element::{AttributeMutation, CustomElementCreationMode, Element, ElementCreator};
65use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
66use crate::dom::eventtarget::EventTarget;
67use crate::dom::file::File;
68use crate::dom::filelist::FileList;
69use crate::dom::globalscope::GlobalScope;
70use crate::dom::html::htmldatalistelement::HTMLDataListElement;
71use crate::dom::html::htmlelement::HTMLElement;
72use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
73use crate::dom::html::htmlformelement::{
74 FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom,
75 SubmittedFrom,
76};
77use crate::dom::keyboardevent::KeyboardEvent;
78use crate::dom::node::{
79 BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext,
80};
81use crate::dom::nodelist::NodeList;
82use crate::dom::text::Text;
83use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
84use crate::dom::types::{CharacterData, FocusEvent};
85use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
86use crate::dom::validitystate::{ValidationFlags, ValidityState};
87use crate::dom::virtualmethods::VirtualMethods;
88use crate::realms::enter_realm;
89use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
90use crate::textinput::{
91 ClipboardEventFlags, IsComposing, KeyReaction, SelectionDirection, TextInput,
92};
93
94const DEFAULT_SUBMIT_VALUE: &str = "Submit";
95const DEFAULT_RESET_VALUE: &str = "Reset";
96const PASSWORD_REPLACEMENT_CHAR: char = '●';
97const DEFAULT_FILE_INPUT_VALUE: &str = "No file chosen";
98
99#[derive(Clone, JSTraceable, MallocSizeOf)]
100#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
101struct TextValueShadowTree {
102 value: Dom<Text>,
103}
104
105impl TextValueShadowTree {
106 fn new(shadow_root: &Node, can_gc: CanGc) -> Self {
107 let value = Text::new(Default::default(), &shadow_root.owner_document(), can_gc);
108 Node::replace_all(Some(value.upcast()), shadow_root, can_gc);
109 Self {
110 value: value.as_traced(),
111 }
112 }
113
114 fn update(&self, input_element: &HTMLInputElement) {
115 let character_data = self.value.upcast::<CharacterData>();
116 let value = input_element.value_for_shadow_dom();
117 if character_data.Data() != value {
118 character_data.SetData(value);
119 }
120 }
121}
122
123#[derive(Clone, JSTraceable, MallocSizeOf)]
124#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
125struct TextInputWidgetShadowTree {
145 inner_container: Dom<Element>,
146 text_container: Dom<Element>,
147 placeholder_container: DomRefCell<Option<Dom<Element>>>,
148}
149
150impl TextInputWidgetShadowTree {
151 fn new(shadow_root: &Node, can_gc: CanGc) -> Self {
152 let document = shadow_root.owner_document();
153 let inner_container = Element::create(
154 QualName::new(None, ns!(html), local_name!("div")),
155 None,
156 &document,
157 ElementCreator::ScriptCreated,
158 CustomElementCreationMode::Asynchronous,
159 None,
160 can_gc,
161 );
162
163 Node::replace_all(Some(inner_container.upcast()), shadow_root.upcast(), can_gc);
164 inner_container
165 .upcast::<Node>()
166 .set_implemented_pseudo_element(PseudoElement::ServoTextControlInnerContainer);
167
168 let text_container = create_ua_widget_div_with_text_node(
169 &document,
170 inner_container.upcast(),
171 PseudoElement::ServoTextControlInnerEditor,
172 false,
173 can_gc,
174 );
175
176 Self {
177 inner_container: inner_container.as_traced(),
178 text_container: text_container.as_traced(),
179 placeholder_container: DomRefCell::new(None),
180 }
181 }
182
183 fn init_placeholder_container_if_necessary(
186 &self,
187 host: &HTMLInputElement,
188 can_gc: CanGc,
189 ) -> Option<DomRoot<Element>> {
190 if let Some(placeholder_container) = &*self.placeholder_container.borrow() {
191 return Some(placeholder_container.root_element());
192 }
193 if host.placeholder.borrow().is_empty() {
196 return None;
197 }
198
199 let placeholder_container = create_ua_widget_div_with_text_node(
200 &host.owner_document(),
201 self.inner_container.upcast::<Node>(),
202 PseudoElement::Placeholder,
203 true,
204 can_gc,
205 );
206 *self.placeholder_container.borrow_mut() = Some(placeholder_container.as_traced());
207 Some(placeholder_container)
208 }
209
210 fn placeholder_character_data(
211 &self,
212 input_element: &HTMLInputElement,
213 can_gc: CanGc,
214 ) -> Option<DomRoot<CharacterData>> {
215 self.init_placeholder_container_if_necessary(input_element, can_gc)
216 .and_then(|placeholder_container| {
217 let first_child = placeholder_container.upcast::<Node>().GetFirstChild()?;
218 Some(DomRoot::from_ref(first_child.downcast::<CharacterData>()?))
219 })
220 }
221
222 fn update_placeholder(&self, input_element: &HTMLInputElement, can_gc: CanGc) {
223 if let Some(character_data) = self.placeholder_character_data(input_element, can_gc) {
224 let placeholder_value = input_element.placeholder.borrow().clone();
225 if character_data.Data() != placeholder_value {
226 character_data.SetData(placeholder_value.clone());
227 }
228 }
229 }
230
231 fn value_character_data(&self) -> Option<DomRoot<CharacterData>> {
232 Some(DomRoot::from_ref(
233 self.text_container
234 .upcast::<Node>()
235 .GetFirstChild()?
236 .downcast::<CharacterData>()?,
237 ))
238 }
239
240 fn update(&self, input_element: &HTMLInputElement) {
243 let value = input_element.Value();
252 let value_text = match (value.is_empty(), input_element.input_type()) {
253 (false, InputType::Password) => value
255 .str()
256 .chars()
257 .map(|_| PASSWORD_REPLACEMENT_CHAR)
258 .collect::<String>()
259 .into(),
260 (false, _) => value,
261 (true, _) => "\u{200B}".into(),
262 };
263
264 if let Some(character_data) = self.value_character_data() {
265 if character_data.Data() != value_text {
266 character_data.SetData(value_text);
267 }
268 }
269 }
270}
271
272#[derive(Clone, JSTraceable, MallocSizeOf)]
273#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
274struct ColorInputShadowTree {
279 color_value: Dom<Element>,
280}
281
282impl ColorInputShadowTree {
283 fn new(shadow_root: &Node, can_gc: CanGc) -> Self {
284 let color_value = Element::create(
285 QualName::new(None, ns!(html), local_name!("div")),
286 None,
287 &shadow_root.owner_document(),
288 ElementCreator::ScriptCreated,
289 CustomElementCreationMode::Asynchronous,
290 None,
291 can_gc,
292 );
293
294 Node::replace_all(Some(color_value.upcast()), shadow_root.upcast(), can_gc);
295 color_value
296 .upcast::<Node>()
297 .set_implemented_pseudo_element(PseudoElement::ColorSwatch);
298
299 Self {
300 color_value: color_value.as_traced(),
301 }
302 }
303
304 fn update(&self, input_element: &HTMLInputElement, can_gc: CanGc) {
305 let mut value = input_element.Value();
306 if value.str().is_valid_simple_color_string() {
307 value.make_ascii_lowercase();
308 } else {
309 value = DOMString::from("#000000");
310 }
311 let style = format!("background-color: {value}");
312 self.color_value
313 .set_string_attribute(&local_name!("style"), style.into(), can_gc);
314 }
315}
316
317#[derive(Clone, JSTraceable, MallocSizeOf)]
318#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
319#[non_exhaustive]
320enum InputElementShadowTree {
321 ColorInput(ColorInputShadowTree),
322 TextInput(TextInputWidgetShadowTree),
323 TextValue(TextValueShadowTree),
324 }
326
327impl InputElementShadowTree {
328 fn new(input_element: &HTMLInputElement, can_gc: CanGc) -> Self {
329 let element = input_element.upcast::<Element>();
330 let shadow_root = element
331 .shadow_root()
332 .unwrap_or_else(|| element.attach_ua_shadow_root(true, can_gc));
333 let shadow_root = shadow_root.upcast();
334
335 if input_element.input_type() == InputType::Color {
336 return Self::ColorInput(ColorInputShadowTree::new(shadow_root, can_gc));
337 }
338 if input_element.renders_as_text_input_widget() {
339 return Self::TextInput(TextInputWidgetShadowTree::new(shadow_root, can_gc));
340 }
341 Self::TextValue(TextValueShadowTree::new(shadow_root, can_gc))
342 }
343
344 fn is_valid_for_element(&self, input_element: &HTMLInputElement) -> bool {
345 if input_element.input_type() == InputType::Color {
346 return matches!(self, InputElementShadowTree::ColorInput(_));
347 }
348 if input_element.renders_as_text_input_widget() {
349 return matches!(self, InputElementShadowTree::TextInput(_));
350 }
351 matches!(self, InputElementShadowTree::TextValue(_))
352 }
353
354 fn update_placeholder_contents(&self, input_element: &HTMLInputElement, can_gc: CanGc) {
355 if let InputElementShadowTree::TextInput(shadow_tree) = self {
356 shadow_tree.update_placeholder(input_element, can_gc);
357 }
358 }
359
360 fn update(&self, input_element: &HTMLInputElement, can_gc: CanGc) {
361 match self {
362 InputElementShadowTree::ColorInput(shadow_tree) => {
363 shadow_tree.update(input_element, can_gc)
364 },
365 InputElementShadowTree::TextInput(shadow_tree) => shadow_tree.update(input_element),
366 InputElementShadowTree::TextValue(shadow_tree) => shadow_tree.update(input_element),
367 }
368 }
369}
370
371fn create_ua_widget_div_with_text_node(
374 document: &Document,
375 parent: &Node,
376 implemented_pseudo: PseudoElement,
377 as_first_child: bool,
378 can_gc: CanGc,
379) -> DomRoot<Element> {
380 let el = Element::create(
381 QualName::new(None, ns!(html), local_name!("div")),
382 None,
383 document,
384 ElementCreator::ScriptCreated,
385 CustomElementCreationMode::Asynchronous,
386 None,
387 can_gc,
388 );
389
390 parent
391 .upcast::<Node>()
392 .AppendChild(el.upcast::<Node>(), can_gc)
393 .unwrap();
394 el.upcast::<Node>()
395 .set_implemented_pseudo_element(implemented_pseudo);
396 let text_node = document.CreateTextNode("".into(), can_gc);
397
398 if !as_first_child {
399 el.upcast::<Node>()
400 .AppendChild(text_node.upcast::<Node>(), can_gc)
401 .unwrap();
402 } else {
403 el.upcast::<Node>()
404 .InsertBefore(
405 text_node.upcast::<Node>(),
406 el.upcast::<Node>().GetFirstChild().as_deref(),
407 can_gc,
408 )
409 .unwrap();
410 }
411 el
412}
413
414#[derive(Clone, Copy, Debug, Default, JSTraceable, PartialEq, MallocSizeOf)]
416pub(crate) enum InputType {
417 Button,
419
420 Checkbox,
422
423 Color,
425
426 Date,
428
429 DatetimeLocal,
431
432 Email,
434
435 File,
437
438 Hidden,
440
441 Image,
443
444 Month,
446
447 Number,
449
450 Password,
452
453 Radio,
455
456 Range,
458
459 Reset,
461
462 Search,
464
465 Submit,
467
468 Tel,
470
471 #[default]
473 Text,
474
475 Time,
477
478 Url,
480
481 Week,
483}
484
485impl InputType {
486 pub(crate) fn is_textual(&self) -> bool {
491 matches!(
492 *self,
493 InputType::Date |
494 InputType::DatetimeLocal |
495 InputType::Email |
496 InputType::Hidden |
497 InputType::Month |
498 InputType::Number |
499 InputType::Range |
500 InputType::Search |
501 InputType::Tel |
502 InputType::Text |
503 InputType::Time |
504 InputType::Url |
505 InputType::Week
506 )
507 }
508
509 fn is_textual_or_password(&self) -> bool {
510 self.is_textual() || *self == InputType::Password
511 }
512
513 fn has_periodic_domain(&self) -> bool {
515 *self == InputType::Time
516 }
517
518 fn as_str(&self) -> &str {
519 match *self {
520 InputType::Button => "button",
521 InputType::Checkbox => "checkbox",
522 InputType::Color => "color",
523 InputType::Date => "date",
524 InputType::DatetimeLocal => "datetime-local",
525 InputType::Email => "email",
526 InputType::File => "file",
527 InputType::Hidden => "hidden",
528 InputType::Image => "image",
529 InputType::Month => "month",
530 InputType::Number => "number",
531 InputType::Password => "password",
532 InputType::Radio => "radio",
533 InputType::Range => "range",
534 InputType::Reset => "reset",
535 InputType::Search => "search",
536 InputType::Submit => "submit",
537 InputType::Tel => "tel",
538 InputType::Text => "text",
539 InputType::Time => "time",
540 InputType::Url => "url",
541 InputType::Week => "week",
542 }
543 }
544}
545
546impl TryFrom<InputType> for InputMethodType {
547 type Error = &'static str;
548
549 fn try_from(input_type: InputType) -> Result<Self, Self::Error> {
550 match input_type {
551 InputType::Color => Ok(InputMethodType::Color),
552 InputType::Date => Ok(InputMethodType::Date),
553 InputType::DatetimeLocal => Ok(InputMethodType::DatetimeLocal),
554 InputType::Email => Ok(InputMethodType::Email),
555 InputType::Month => Ok(InputMethodType::Month),
556 InputType::Number => Ok(InputMethodType::Number),
557 InputType::Password => Ok(InputMethodType::Password),
558 InputType::Search => Ok(InputMethodType::Search),
559 InputType::Tel => Ok(InputMethodType::Tel),
560 InputType::Text => Ok(InputMethodType::Text),
561 InputType::Time => Ok(InputMethodType::Time),
562 InputType::Url => Ok(InputMethodType::Url),
563 InputType::Week => Ok(InputMethodType::Week),
564 _ => Err("Input does not support IME."),
565 }
566 }
567}
568
569impl From<&Atom> for InputType {
570 fn from(value: &Atom) -> InputType {
571 match value.to_ascii_lowercase() {
572 atom!("button") => InputType::Button,
573 atom!("checkbox") => InputType::Checkbox,
574 atom!("color") => InputType::Color,
575 atom!("date") => InputType::Date,
576 atom!("datetime-local") => InputType::DatetimeLocal,
577 atom!("email") => InputType::Email,
578 atom!("file") => InputType::File,
579 atom!("hidden") => InputType::Hidden,
580 atom!("image") => InputType::Image,
581 atom!("month") => InputType::Month,
582 atom!("number") => InputType::Number,
583 atom!("password") => InputType::Password,
584 atom!("radio") => InputType::Radio,
585 atom!("range") => InputType::Range,
586 atom!("reset") => InputType::Reset,
587 atom!("search") => InputType::Search,
588 atom!("submit") => InputType::Submit,
589 atom!("tel") => InputType::Tel,
590 atom!("text") => InputType::Text,
591 atom!("time") => InputType::Time,
592 atom!("url") => InputType::Url,
593 atom!("week") => InputType::Week,
594 _ => Self::default(),
595 }
596 }
597}
598
599#[derive(Debug, PartialEq)]
600enum ValueMode {
601 Value,
603
604 Default,
606
607 DefaultOn,
609
610 Filename,
612}
613
614#[derive(Debug, PartialEq)]
615enum StepDirection {
616 Up,
617 Down,
618}
619
620#[dom_struct]
621pub(crate) struct HTMLInputElement {
622 htmlelement: HTMLElement,
623 input_type: Cell<InputType>,
624
625 checked_changed: Cell<bool>,
627 placeholder: DomRefCell<DOMString>,
628 size: Cell<u32>,
629 maxlength: Cell<i32>,
630 minlength: Cell<i32>,
631 #[no_trace]
632 textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
633 value_dirty: Cell<bool>,
635 sanitization_flag: Cell<bool>,
639
640 filelist: MutNullableDom<FileList>,
641 form_owner: MutNullableDom<HTMLFormElement>,
642 labels_node_list: MutNullableDom<NodeList>,
643 validity_state: MutNullableDom<ValidityState>,
644 shadow_tree: DomRefCell<Option<InputElementShadowTree>>,
645 #[no_trace]
646 pending_webdriver_response: RefCell<Option<PendingWebDriverResponse>>,
647}
648
649#[derive(JSTraceable)]
650pub(crate) struct InputActivationState {
651 indeterminate: bool,
652 checked: bool,
653 checked_radio: Option<DomRoot<HTMLInputElement>>,
654 old_type: InputType,
656 }
658
659static DEFAULT_INPUT_SIZE: u32 = 20;
660static DEFAULT_MAX_LENGTH: i32 = -1;
661static DEFAULT_MIN_LENGTH: i32 = -1;
662
663#[expect(non_snake_case)]
664impl HTMLInputElement {
665 fn new_inherited(
666 local_name: LocalName,
667 prefix: Option<Prefix>,
668 document: &Document,
669 ) -> HTMLInputElement {
670 let embedder_sender = document
671 .window()
672 .as_global_scope()
673 .script_to_embedder_chan()
674 .clone();
675 HTMLInputElement {
676 htmlelement: HTMLElement::new_inherited_with_state(
677 ElementState::ENABLED | ElementState::READWRITE,
678 local_name,
679 prefix,
680 document,
681 ),
682 input_type: Cell::new(Default::default()),
683 placeholder: DomRefCell::new(DOMString::new()),
684 checked_changed: Cell::new(false),
685 maxlength: Cell::new(DEFAULT_MAX_LENGTH),
686 minlength: Cell::new(DEFAULT_MIN_LENGTH),
687 size: Cell::new(DEFAULT_INPUT_SIZE),
688 textinput: DomRefCell::new(TextInput::new(
689 Lines::Single,
690 DOMString::new(),
691 EmbedderClipboardProvider {
692 embedder_sender,
693 webview_id: document.webview_id(),
694 },
695 None,
696 None,
697 SelectionDirection::None,
698 )),
699 value_dirty: Cell::new(false),
700 sanitization_flag: Cell::new(true),
701 filelist: MutNullableDom::new(None),
702 form_owner: Default::default(),
703 labels_node_list: MutNullableDom::new(None),
704 validity_state: Default::default(),
705 shadow_tree: Default::default(),
706 pending_webdriver_response: Default::default(),
707 }
708 }
709
710 pub(crate) fn new(
711 local_name: LocalName,
712 prefix: Option<Prefix>,
713 document: &Document,
714 proto: Option<HandleObject>,
715 can_gc: CanGc,
716 ) -> DomRoot<HTMLInputElement> {
717 Node::reflect_node_with_proto(
718 Box::new(HTMLInputElement::new_inherited(
719 local_name, prefix, document,
720 )),
721 document,
722 proto,
723 can_gc,
724 )
725 }
726
727 pub(crate) fn auto_directionality(&self) -> Option<String> {
728 match self.input_type() {
729 InputType::Text | InputType::Search | InputType::Url | InputType::Email => {
730 let value: String = self.Value().to_string();
731 Some(HTMLInputElement::directionality_from_value(&value))
732 },
733 _ => None,
734 }
735 }
736
737 pub(crate) fn directionality_from_value(value: &str) -> String {
738 if HTMLInputElement::is_first_strong_character_rtl(value) {
739 "rtl".to_owned()
740 } else {
741 "ltr".to_owned()
742 }
743 }
744
745 fn is_first_strong_character_rtl(value: &str) -> bool {
746 for ch in value.chars() {
747 return match bidi_class(ch) {
748 BidiClass::L => false,
749 BidiClass::AL => true,
750 BidiClass::R => true,
751 _ => continue,
752 };
753 }
754 false
755 }
756
757 fn value_mode(&self) -> ValueMode {
760 match self.input_type() {
761 InputType::Submit |
762 InputType::Reset |
763 InputType::Button |
764 InputType::Image |
765 InputType::Hidden => ValueMode::Default,
766
767 InputType::Checkbox | InputType::Radio => ValueMode::DefaultOn,
768
769 InputType::Color |
770 InputType::Date |
771 InputType::DatetimeLocal |
772 InputType::Email |
773 InputType::Month |
774 InputType::Number |
775 InputType::Password |
776 InputType::Range |
777 InputType::Search |
778 InputType::Tel |
779 InputType::Text |
780 InputType::Time |
781 InputType::Url |
782 InputType::Week => ValueMode::Value,
783
784 InputType::File => ValueMode::Filename,
785 }
786 }
787
788 #[inline]
789 pub(crate) fn input_type(&self) -> InputType {
790 self.input_type.get()
791 }
792
793 pub(crate) fn is_nontypeable(&self) -> bool {
795 matches!(
796 self.input_type(),
797 InputType::Button |
798 InputType::Checkbox |
799 InputType::Color |
800 InputType::File |
801 InputType::Hidden |
802 InputType::Image |
803 InputType::Radio |
804 InputType::Range |
805 InputType::Reset |
806 InputType::Submit
807 )
808 }
809
810 #[inline]
811 pub(crate) fn is_submit_button(&self) -> bool {
812 let input_type = self.input_type.get();
813 input_type == InputType::Submit || input_type == InputType::Image
814 }
815
816 pub(crate) fn disable_sanitization(&self) {
817 self.sanitization_flag.set(false);
818 }
819
820 pub(crate) fn enable_sanitization(&self) {
821 self.sanitization_flag.set(true);
822 let mut textinput = self.textinput.borrow_mut();
823 let mut value = textinput.get_content();
824 self.sanitize_value(&mut value);
825 textinput.set_content(value);
826 self.upcast::<Node>().dirty(NodeDamage::Other);
827 }
828
829 fn does_minmaxlength_apply(&self) -> bool {
830 matches!(
831 self.input_type(),
832 InputType::Text |
833 InputType::Search |
834 InputType::Url |
835 InputType::Tel |
836 InputType::Email |
837 InputType::Password
838 )
839 }
840
841 fn does_pattern_apply(&self) -> bool {
842 matches!(
843 self.input_type(),
844 InputType::Text |
845 InputType::Search |
846 InputType::Url |
847 InputType::Tel |
848 InputType::Email |
849 InputType::Password
850 )
851 }
852
853 fn does_multiple_apply(&self) -> bool {
854 self.input_type() == InputType::Email
855 }
856
857 fn does_value_as_number_apply(&self) -> bool {
860 matches!(
861 self.input_type(),
862 InputType::Date |
863 InputType::Month |
864 InputType::Week |
865 InputType::Time |
866 InputType::DatetimeLocal |
867 InputType::Number |
868 InputType::Range
869 )
870 }
871
872 fn does_value_as_date_apply(&self) -> bool {
873 matches!(
874 self.input_type(),
875 InputType::Date | InputType::Month | InputType::Week | InputType::Time
876 )
877 }
878
879 fn allowed_value_step(&self) -> Option<f64> {
881 let default_step = self.default_step()?;
884
885 let Some(attribute) = self
888 .upcast::<Element>()
889 .get_attribute(&ns!(), &local_name!("step"))
890 else {
891 return Some(default_step * self.step_scale_factor());
892 };
893
894 if attribute.value().eq_ignore_ascii_case("any") {
897 return None;
898 }
899
900 let Some(parsed_value) =
904 parse_floating_point_number(&attribute.value()).filter(|value| *value > 0.0)
905 else {
906 return Some(default_step * self.step_scale_factor());
907 };
908
909 Some(parsed_value * self.step_scale_factor())
913 }
914
915 fn minimum(&self) -> Option<f64> {
917 self.upcast::<Element>()
918 .get_attribute(&ns!(), &local_name!("min"))
919 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
920 .or_else(|| self.default_minimum())
921 }
922
923 fn maximum(&self) -> Option<f64> {
925 self.upcast::<Element>()
926 .get_attribute(&ns!(), &local_name!("max"))
927 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
928 .or_else(|| self.default_maximum())
929 }
930
931 fn stepped_minimum(&self) -> Option<f64> {
934 match (self.minimum(), self.allowed_value_step()) {
935 (Some(min), Some(allowed_step)) => {
936 let step_base = self.step_base();
937 let nsteps = (min - step_base) / allowed_step;
939 Some(step_base + (allowed_step * nsteps.ceil()))
941 },
942 (_, _) => None,
943 }
944 }
945
946 fn stepped_maximum(&self) -> Option<f64> {
949 match (self.maximum(), self.allowed_value_step()) {
950 (Some(max), Some(allowed_step)) => {
951 let step_base = self.step_base();
952 let nsteps = (max - step_base) / allowed_step;
954 Some(step_base + (allowed_step * nsteps.floor()))
956 },
957 (_, _) => None,
958 }
959 }
960
961 fn default_minimum(&self) -> Option<f64> {
963 match self.input_type() {
964 InputType::Range => Some(0.0),
965 _ => None,
966 }
967 }
968
969 fn default_maximum(&self) -> Option<f64> {
971 match self.input_type() {
972 InputType::Range => Some(100.0),
973 _ => None,
974 }
975 }
976
977 fn default_range_value(&self) -> f64 {
979 let min = self.minimum().unwrap_or(0.0);
980 let max = self.maximum().unwrap_or(100.0);
981 if max < min {
982 min
983 } else {
984 min + (max - min) * 0.5
985 }
986 }
987
988 fn default_step(&self) -> Option<f64> {
990 match self.input_type() {
991 InputType::Date => Some(1.0),
992 InputType::Month => Some(1.0),
993 InputType::Week => Some(1.0),
994 InputType::Time => Some(60.0),
995 InputType::DatetimeLocal => Some(60.0),
996 InputType::Number => Some(1.0),
997 InputType::Range => Some(1.0),
998 _ => None,
999 }
1000 }
1001
1002 fn step_scale_factor(&self) -> f64 {
1004 match self.input_type() {
1005 InputType::Date => 86400000.0,
1006 InputType::Month => 1.0,
1007 InputType::Week => 604800000.0,
1008 InputType::Time => 1000.0,
1009 InputType::DatetimeLocal => 1000.0,
1010 InputType::Number => 1.0,
1011 InputType::Range => 1.0,
1012 _ => unreachable!(),
1013 }
1014 }
1015
1016 fn step_base(&self) -> f64 {
1018 if let Some(minimum) = self
1022 .upcast::<Element>()
1023 .get_attribute(&ns!(), &local_name!("min"))
1024 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
1025 {
1026 return minimum;
1027 }
1028
1029 if let Some(value) = self
1033 .upcast::<Element>()
1034 .get_attribute(&ns!(), &local_name!("value"))
1035 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
1036 {
1037 return value;
1038 }
1039
1040 if let Some(default_step_base) = self.default_step_base() {
1042 return default_step_base;
1043 }
1044
1045 0.0
1047 }
1048
1049 fn default_step_base(&self) -> Option<f64> {
1051 match self.input_type() {
1052 InputType::Week => Some(-259200000.0),
1053 _ => None,
1054 }
1055 }
1056
1057 fn step_up_or_down(&self, n: i32, dir: StepDirection, can_gc: CanGc) -> ErrorResult {
1061 if !self.does_value_as_number_apply() {
1064 return Err(Error::InvalidState(None));
1065 }
1066 let step_base = self.step_base();
1067
1068 let Some(allowed_value_step) = self.allowed_value_step() else {
1070 return Err(Error::InvalidState(None));
1071 };
1072
1073 let minimum = self.minimum();
1076 let maximum = self.maximum();
1077 if let (Some(min), Some(max)) = (minimum, maximum) {
1078 if min > max {
1079 return Ok(());
1080 }
1081
1082 if let Some(stepped_minimum) = self.stepped_minimum() {
1086 if stepped_minimum > max {
1087 return Ok(());
1088 }
1089 }
1090 }
1091
1092 let mut value: f64 = self
1096 .convert_string_to_number(&self.Value().str())
1097 .unwrap_or(0.0);
1098
1099 let valueBeforeStepping = value;
1101
1102 if (value - step_base) % allowed_value_step != 0.0 {
1107 value = match dir {
1108 StepDirection::Down =>
1109 {
1111 let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
1112 intervals_from_base * allowed_value_step + step_base
1113 },
1114 StepDirection::Up =>
1115 {
1117 let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
1118 intervals_from_base * allowed_value_step + step_base
1119 },
1120 };
1121 }
1122 else {
1124 value += match dir {
1129 StepDirection::Down => -f64::from(n) * allowed_value_step,
1130 StepDirection::Up => f64::from(n) * allowed_value_step,
1131 };
1132 }
1133
1134 if let Some(min) = minimum {
1138 if value < min {
1139 value = self.stepped_minimum().unwrap_or(value);
1140 }
1141 }
1142
1143 if let Some(max) = maximum {
1147 if value > max {
1148 value = self.stepped_maximum().unwrap_or(value);
1149 }
1150 }
1151
1152 match dir {
1156 StepDirection::Down => {
1157 if value > valueBeforeStepping {
1158 return Ok(());
1159 }
1160 },
1161 StepDirection::Up => {
1162 if value < valueBeforeStepping {
1163 return Ok(());
1164 }
1165 },
1166 }
1167
1168 self.SetValueAsNumber(value, can_gc)
1172 }
1173
1174 fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
1176 let list_string = self
1177 .upcast::<Element>()
1178 .get_string_attribute(&local_name!("list"));
1179 if list_string.is_empty() {
1180 return None;
1181 }
1182 let ancestor = self
1183 .upcast::<Node>()
1184 .GetRootNode(&GetRootNodeOptions::empty());
1185 let first_with_id = &ancestor
1186 .traverse_preorder(ShadowIncluding::No)
1187 .find(|node| {
1188 node.downcast::<Element>()
1189 .is_some_and(|e| e.Id() == list_string)
1190 });
1191 first_with_id
1192 .as_ref()
1193 .and_then(|el| el.downcast::<HTMLDataListElement>())
1194 .map(DomRoot::from_ref)
1195 }
1196
1197 fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
1199 match self.input_type() {
1200 InputType::Checkbox => self.Required() && !self.Checked(),
1202 InputType::Radio => {
1204 if self.radio_group_name().is_none() {
1205 return false;
1206 }
1207 let mut is_required = self.Required();
1208 let mut is_checked = self.Checked();
1209 let root = self
1210 .upcast::<Node>()
1211 .GetRootNode(&GetRootNodeOptions::empty());
1212 let form = self.form_owner();
1213 for other in radio_group_iter(
1214 self,
1215 self.radio_group_name().as_ref(),
1216 form.as_deref(),
1217 &root,
1218 ) {
1219 is_required = is_required || other.Required();
1220 is_checked = is_checked || other.Checked();
1221 }
1222 is_required && !is_checked
1223 },
1224 InputType::File => {
1226 self.Required() && self.filelist.get().is_none_or(|files| files.Length() == 0)
1227 },
1228 _ => {
1230 self.Required() &&
1231 self.value_mode() == ValueMode::Value &&
1232 self.is_mutable() &&
1233 value.is_empty()
1234 },
1235 }
1236 }
1237
1238 fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
1240 if value.is_empty() {
1241 return false;
1242 }
1243
1244 match self.input_type() {
1245 InputType::Url => Url::parse(&value.str()).is_err(),
1247 InputType::Email => {
1250 if self.Multiple() {
1251 !split_commas(&value.str()).all(|string| string.is_valid_email_address_string())
1252 } else {
1253 !value.str().is_valid_email_address_string()
1254 }
1255 },
1256 _ => false,
1258 }
1259 }
1260
1261 fn suffers_from_pattern_mismatch(&self, value: &DOMString, can_gc: CanGc) -> bool {
1263 let pattern_str = self.Pattern();
1266 if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
1267 return false;
1268 }
1269
1270 let cx = GlobalScope::get_cx();
1272 let _ac = enter_realm(self);
1273 rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>());
1274
1275 if compile_pattern(cx, &pattern_str.str(), pattern.handle_mut(), can_gc) {
1276 if self.Multiple() && self.does_multiple_apply() {
1277 !split_commas(&value.str())
1278 .all(|s| matches_js_regex(cx, pattern.handle(), s, can_gc).unwrap_or(true))
1279 } else {
1280 !matches_js_regex(cx, pattern.handle(), &value.str(), can_gc).unwrap_or(true)
1281 }
1282 } else {
1283 false
1285 }
1286 }
1287
1288 fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
1290 if value.is_empty() {
1291 return false;
1292 }
1293
1294 match self.input_type() {
1295 InputType::Email => {
1298 false
1302 },
1303 InputType::Date => !value.str().is_valid_date_string(),
1305 InputType::Month => !value.str().is_valid_month_string(),
1307 InputType::Week => !value.str().is_valid_week_string(),
1309 InputType::Time => !value.str().is_valid_time_string(),
1311 InputType::DatetimeLocal => !value.str().is_valid_local_date_time_string(),
1313 InputType::Number | InputType::Range => !value.is_valid_floating_point_number_string(),
1316 InputType::Color => !value.str().is_valid_simple_color_string(),
1318 _ => false,
1320 }
1321 }
1322
1323 fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
1326 let value_dirty = self.value_dirty.get();
1329 let textinput = self.textinput.borrow();
1330 let edit_by_user = !textinput.was_last_change_by_set_content();
1331
1332 if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
1333 return ValidationFlags::empty();
1334 }
1335
1336 let mut failed_flags = ValidationFlags::empty();
1337 let Utf16CodeUnitLength(value_len) = textinput.len_utf16();
1338 let min_length = self.MinLength();
1339 let max_length = self.MaxLength();
1340
1341 if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
1342 failed_flags.insert(ValidationFlags::TOO_SHORT);
1343 }
1344
1345 if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
1346 failed_flags.insert(ValidationFlags::TOO_LONG);
1347 }
1348
1349 failed_flags
1350 }
1351
1352 fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
1356 if value.is_empty() || !self.does_value_as_number_apply() {
1357 return ValidationFlags::empty();
1358 }
1359
1360 let Some(value_as_number) = self.convert_string_to_number(&value.str()) else {
1361 return ValidationFlags::empty();
1362 };
1363
1364 let mut failed_flags = ValidationFlags::empty();
1365 let min_value = self.minimum();
1366 let max_value = self.maximum();
1367
1368 let has_reversed_range = match (min_value, max_value) {
1370 (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
1371 _ => false,
1372 };
1373
1374 if has_reversed_range {
1375 if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
1377 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1378 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1379 }
1380 } else {
1381 if let Some(min_value) = min_value {
1383 if value_as_number < min_value {
1384 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1385 }
1386 }
1387 if let Some(max_value) = max_value {
1389 if value_as_number > max_value {
1390 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1391 }
1392 }
1393 }
1394
1395 if let Some(step) = self.allowed_value_step() {
1397 let diff = (self.step_base() - value_as_number) % step / value_as_number;
1401 if diff.abs() > 1e-12 {
1402 failed_flags.insert(ValidationFlags::STEP_MISMATCH);
1403 }
1404 }
1405
1406 failed_flags
1407 }
1408
1409 fn get_or_create_shadow_tree(&self, can_gc: CanGc) -> Ref<'_, InputElementShadowTree> {
1412 {
1413 if let Ok(shadow_tree) = Ref::filter_map(self.shadow_tree.borrow(), |shadow_tree| {
1414 shadow_tree
1415 .as_ref()
1416 .filter(|shadow_tree| shadow_tree.is_valid_for_element(self))
1417 }) {
1418 return shadow_tree;
1419 }
1420 }
1421 *self.shadow_tree.borrow_mut() = Some(InputElementShadowTree::new(self, can_gc));
1422 self.get_or_create_shadow_tree(can_gc)
1423 }
1424
1425 pub(crate) fn renders_as_text_input_widget(&self) -> bool {
1430 matches!(
1431 self.input_type(),
1432 InputType::Date |
1433 InputType::DatetimeLocal |
1434 InputType::Email |
1435 InputType::Month |
1436 InputType::Number |
1437 InputType::Password |
1438 InputType::Range |
1439 InputType::Search |
1440 InputType::Tel |
1441 InputType::Text |
1442 InputType::Time |
1443 InputType::Url |
1444 InputType::Week
1445 )
1446 }
1447
1448 fn may_have_embedder_control(&self) -> bool {
1449 let el = self.upcast::<Element>();
1450 self.input_type() == InputType::Color && !el.disabled_state()
1451 }
1452
1453 fn handle_key_reaction(&self, action: KeyReaction, event: &Event, can_gc: CanGc) {
1454 match action {
1455 KeyReaction::TriggerDefaultAction => {
1456 self.implicit_submission(can_gc);
1457 },
1458 KeyReaction::DispatchInput(text, is_composing, input_type) => {
1459 if event.IsTrusted() {
1460 self.textinput.borrow().queue_input_event(
1461 self.upcast(),
1462 text,
1463 is_composing,
1464 input_type,
1465 );
1466 }
1467 self.value_dirty.set(true);
1468 self.update_placeholder_shown_state();
1469 self.upcast::<Node>().dirty(NodeDamage::Other);
1470 event.mark_as_handled();
1471 },
1472 KeyReaction::RedrawSelection => {
1473 self.upcast::<Node>().dirty(NodeDamage::Other);
1474 event.mark_as_handled();
1475 },
1476 KeyReaction::Nothing => (),
1477 }
1478 }
1479
1480 fn value_for_shadow_dom(&self) -> DOMString {
1482 let input_type = self.input_type();
1483 match input_type {
1484 InputType::Checkbox |
1485 InputType::Radio |
1486 InputType::Image |
1487 InputType::Hidden |
1488 InputType::Range => "".into(),
1489 InputType::File => {
1490 let Some(filelist) = self.filelist.get() else {
1491 return DEFAULT_FILE_INPUT_VALUE.into();
1492 };
1493 let length = filelist.Length();
1494 if length > 1 {
1495 return format!("{length} files").into();
1496 }
1497
1498 let Some(first_item) = filelist.Item(0) else {
1499 return DEFAULT_FILE_INPUT_VALUE.into();
1500 };
1501 first_item.name().to_string().into()
1502 },
1503 _ => {
1504 if let Some(attribute_value) = self
1505 .upcast::<Element>()
1506 .get_attribute(&ns!(), &local_name!("value"))
1507 .map(|attribute| attribute.Value())
1508 {
1509 return attribute_value;
1510 }
1511 match input_type {
1512 InputType::Submit => DEFAULT_SUBMIT_VALUE.into(),
1513 InputType::Reset => DEFAULT_RESET_VALUE.into(),
1514 _ => "".into(),
1515 }
1516 },
1517 }
1518 }
1519}
1520
1521pub(crate) trait LayoutHTMLInputElementHelpers<'dom> {
1522 fn size_for_layout(self) -> u32;
1523 fn selection_for_layout(self) -> Option<Range<usize>>;
1524}
1525
1526#[expect(unsafe_code)]
1527impl<'dom> LayoutDom<'dom, HTMLInputElement> {
1528 fn get_raw_textinput_value(self) -> DOMString {
1529 unsafe {
1530 self.unsafe_get()
1531 .textinput
1532 .borrow_for_layout()
1533 .get_content()
1534 }
1535 }
1536
1537 fn input_type(self) -> InputType {
1538 self.unsafe_get().input_type.get()
1539 }
1540
1541 fn textinput_sorted_selection_offsets_range(self) -> Range<Utf8CodeUnitLength> {
1542 unsafe {
1543 self.unsafe_get()
1544 .textinput
1545 .borrow_for_layout()
1546 .sorted_selection_offsets_range()
1547 }
1548 }
1549}
1550
1551impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElement> {
1552 fn size_for_layout(self) -> u32 {
1562 self.unsafe_get().size.get()
1563 }
1564
1565 fn selection_for_layout(self) -> Option<Range<usize>> {
1566 if !self.upcast::<Element>().focus_state() {
1567 return None;
1568 }
1569
1570 let sorted_selection_offsets_range = self.textinput_sorted_selection_offsets_range();
1571
1572 match self.input_type() {
1573 InputType::Password => {
1574 let text = self.get_raw_textinput_value();
1575 let sel = Utf8CodeUnitLength::unwrap_range(sorted_selection_offsets_range);
1576
1577 let char_start = text.str()[..sel.start].chars().count();
1579 let char_end = char_start + text.str()[sel].chars().count();
1580
1581 let bytes_per_char = PASSWORD_REPLACEMENT_CHAR.len_utf8();
1582 Some(char_start * bytes_per_char..char_end * bytes_per_char)
1583 },
1584 input_type if input_type.is_textual() => Some(Utf8CodeUnitLength::unwrap_range(
1585 sorted_selection_offsets_range,
1586 )),
1587 _ => None,
1588 }
1589 }
1590}
1591
1592impl TextControlElement for HTMLInputElement {
1593 fn selection_api_applies(&self) -> bool {
1595 matches!(
1596 self.input_type(),
1597 InputType::Text |
1598 InputType::Search |
1599 InputType::Url |
1600 InputType::Tel |
1601 InputType::Password
1602 )
1603 }
1604
1605 fn has_selectable_text(&self) -> bool {
1613 self.renders_as_text_input_widget() && !self.textinput.borrow().get_content().is_empty()
1614 }
1615
1616 fn has_selection(&self) -> bool {
1617 self.textinput.borrow().has_selection()
1618 }
1619
1620 fn set_dirty_value_flag(&self, value: bool) {
1621 self.value_dirty.set(value)
1622 }
1623
1624 fn select_all(&self) {
1625 self.textinput.borrow_mut().select_all();
1626 self.upcast::<Node>().dirty(NodeDamage::Other);
1627 }
1628}
1629
1630impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1631 make_getter!(Accept, "accept");
1633
1634 make_setter!(SetAccept, "accept");
1636
1637 make_getter!(Alt, "alt");
1639
1640 make_setter!(SetAlt, "alt");
1642
1643 make_getter!(DirName, "dirname");
1645
1646 make_setter!(SetDirName, "dirname");
1648
1649 make_bool_getter!(Disabled, "disabled");
1651
1652 make_bool_setter!(SetDisabled, "disabled");
1654
1655 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1657 self.form_owner()
1658 }
1659
1660 fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1662 self.filelist.get().as_ref().cloned()
1663 }
1664
1665 fn SetFiles(&self, files: Option<&FileList>) {
1667 if self.input_type() == InputType::File && files.is_some() {
1668 self.filelist.set(files);
1669 }
1670 }
1671
1672 make_bool_getter!(DefaultChecked, "checked");
1674
1675 make_bool_setter!(SetDefaultChecked, "checked");
1677
1678 fn Checked(&self) -> bool {
1680 self.upcast::<Element>()
1681 .state()
1682 .contains(ElementState::CHECKED)
1683 }
1684
1685 fn SetChecked(&self, checked: bool, can_gc: CanGc) {
1687 self.update_checked_state(checked, true, can_gc);
1688 self.value_changed(can_gc);
1689 }
1690
1691 make_bool_getter!(ReadOnly, "readonly");
1693
1694 make_bool_setter!(SetReadOnly, "readonly");
1696
1697 make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1699
1700 make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1702
1703 fn Type(&self) -> DOMString {
1705 DOMString::from(self.input_type().as_str())
1706 }
1707
1708 make_atomic_setter!(SetType, "type");
1710
1711 fn Value(&self) -> DOMString {
1713 match self.value_mode() {
1714 ValueMode::Value => self.textinput.borrow().get_content(),
1715 ValueMode::Default => self
1716 .upcast::<Element>()
1717 .get_attribute(&ns!(), &local_name!("value"))
1718 .map_or(DOMString::from(""), |a| {
1719 DOMString::from(a.summarize().value)
1720 }),
1721 ValueMode::DefaultOn => self
1722 .upcast::<Element>()
1723 .get_attribute(&ns!(), &local_name!("value"))
1724 .map_or(DOMString::from("on"), |a| {
1725 DOMString::from(a.summarize().value)
1726 }),
1727 ValueMode::Filename => {
1728 let mut path = DOMString::from("");
1729 match self.filelist.get() {
1730 Some(ref fl) => match fl.Item(0) {
1731 Some(ref f) => {
1732 path.push_str("C:\\fakepath\\");
1733 path.push_str(&f.name().str());
1734 path
1735 },
1736 None => path,
1737 },
1738 None => path,
1739 }
1740 },
1741 }
1742 }
1743
1744 fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult {
1746 match self.value_mode() {
1747 ValueMode::Value => {
1748 {
1749 self.value_dirty.set(true);
1751
1752 self.sanitize_value(&mut value);
1754
1755 let mut textinput = self.textinput.borrow_mut();
1756
1757 if textinput.get_content() != value {
1759 textinput.set_content(value);
1761
1762 textinput.clear_selection_to_end();
1764 }
1765 }
1766
1767 self.update_placeholder_shown_state();
1771 },
1772 ValueMode::Default | ValueMode::DefaultOn => {
1773 self.upcast::<Element>()
1774 .set_string_attribute(&local_name!("value"), value, can_gc);
1775 },
1776 ValueMode::Filename => {
1777 if value.is_empty() {
1778 let window = self.owner_window();
1779 let fl = FileList::new(&window, vec![], can_gc);
1780 self.filelist.set(Some(&fl));
1781 } else {
1782 return Err(Error::InvalidState(None));
1783 }
1784 },
1785 }
1786
1787 self.value_changed(can_gc);
1788 self.upcast::<Node>().dirty(NodeDamage::Other);
1789 Ok(())
1790 }
1791
1792 make_getter!(DefaultValue, "value");
1794
1795 make_setter!(SetDefaultValue, "value");
1797
1798 make_getter!(Min, "min");
1800
1801 make_setter!(SetMin, "min");
1803
1804 fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1806 self.suggestions_source_element()
1807 }
1808
1809 #[expect(unsafe_code)]
1811 fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> {
1812 self.convert_string_to_naive_datetime(self.Value())
1813 .map(|date_time| unsafe {
1814 let time = ClippedTime {
1815 t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1816 };
1817 NonNull::new_unchecked(NewDateObject(*cx, time))
1818 })
1819 }
1820
1821 #[expect(non_snake_case)]
1823 #[expect(unsafe_code)]
1824 fn SetValueAsDate(
1825 &self,
1826 cx: SafeJSContext,
1827 value: *mut JSObject,
1828 can_gc: CanGc,
1829 ) -> ErrorResult {
1830 rooted!(in(*cx) let value = value);
1831 if !self.does_value_as_date_apply() {
1832 return Err(Error::InvalidState(None));
1833 }
1834 if value.is_null() {
1835 return self.SetValue(DOMString::from(""), can_gc);
1836 }
1837 let mut msecs: f64 = 0.0;
1838 unsafe {
1842 let mut isDate = false;
1843 if !ObjectIsDate(*cx, Handle::from(value.handle()), &mut isDate) {
1844 return Err(Error::JSFailed);
1845 }
1846 if !isDate {
1847 return Err(Error::Type("Value was not a date".to_string()));
1848 }
1849 if !DateGetMsecSinceEpoch(*cx, Handle::from(value.handle()), &mut msecs) {
1850 return Err(Error::JSFailed);
1851 }
1852 if !msecs.is_finite() {
1853 return self.SetValue(DOMString::from(""), can_gc);
1854 }
1855 }
1856
1857 let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1858 return self.SetValue(DOMString::from(""), can_gc);
1859 };
1860 self.SetValue(self.convert_datetime_to_dom_string(date_time), can_gc)
1861 }
1862
1863 fn ValueAsNumber(&self) -> f64 {
1865 self.convert_string_to_number(&self.Value().str())
1866 .unwrap_or(f64::NAN)
1867 }
1868
1869 fn SetValueAsNumber(&self, value: f64, can_gc: CanGc) -> ErrorResult {
1871 if value.is_infinite() {
1872 Err(Error::Type("value is not finite".to_string()))
1873 } else if !self.does_value_as_number_apply() {
1874 Err(Error::InvalidState(None))
1875 } else if value.is_nan() {
1876 self.SetValue(DOMString::from(""), can_gc)
1877 } else if let Some(converted) = self.convert_number_to_string(value) {
1878 self.SetValue(converted, can_gc)
1879 } else {
1880 self.SetValue(DOMString::from(""), can_gc)
1885 }
1886 }
1887
1888 make_getter!(Name, "name");
1890
1891 make_atomic_setter!(SetName, "name");
1893
1894 make_getter!(Placeholder, "placeholder");
1896
1897 make_setter!(SetPlaceholder, "placeholder");
1899
1900 make_form_action_getter!(FormAction, "formaction");
1902
1903 make_setter!(SetFormAction, "formaction");
1905
1906 make_enumerated_getter!(
1908 FormEnctype,
1909 "formenctype",
1910 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1911 invalid => "application/x-www-form-urlencoded"
1912 );
1913
1914 make_setter!(SetFormEnctype, "formenctype");
1916
1917 make_enumerated_getter!(
1919 FormMethod,
1920 "formmethod",
1921 "get" | "post" | "dialog",
1922 invalid => "get"
1923 );
1924
1925 make_setter!(SetFormMethod, "formmethod");
1927
1928 make_getter!(FormTarget, "formtarget");
1930
1931 make_setter!(SetFormTarget, "formtarget");
1933
1934 make_bool_getter!(FormNoValidate, "formnovalidate");
1936
1937 make_bool_setter!(SetFormNoValidate, "formnovalidate");
1939
1940 make_getter!(Max, "max");
1942
1943 make_setter!(SetMax, "max");
1945
1946 make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1948
1949 make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1951
1952 make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1954
1955 make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1957
1958 make_bool_getter!(Multiple, "multiple");
1960
1961 make_bool_setter!(SetMultiple, "multiple");
1963
1964 make_getter!(Pattern, "pattern");
1966
1967 make_setter!(SetPattern, "pattern");
1969
1970 make_bool_getter!(Required, "required");
1972
1973 make_bool_setter!(SetRequired, "required");
1975
1976 make_url_getter!(Src, "src");
1978
1979 make_url_setter!(SetSrc, "src");
1981
1982 make_getter!(Step, "step");
1984
1985 make_setter!(SetStep, "step");
1987
1988 fn Indeterminate(&self) -> bool {
1990 self.upcast::<Element>()
1991 .state()
1992 .contains(ElementState::INDETERMINATE)
1993 }
1994
1995 fn SetIndeterminate(&self, val: bool) {
1997 self.upcast::<Element>()
1998 .set_state(ElementState::INDETERMINATE, val)
1999 }
2000
2001 fn GetLabels(&self, can_gc: CanGc) -> Option<DomRoot<NodeList>> {
2005 if self.input_type() == InputType::Hidden {
2006 None
2007 } else {
2008 Some(self.labels_node_list.or_init(|| {
2009 NodeList::new_labels_list(
2010 self.upcast::<Node>().owner_doc().window(),
2011 self.upcast::<HTMLElement>(),
2012 can_gc,
2013 )
2014 }))
2015 }
2016 }
2017
2018 fn Select(&self) {
2020 self.selection().dom_select();
2021 }
2022
2023 fn GetSelectionStart(&self) -> Option<u32> {
2025 self.selection().dom_start().map(|start| start.0 as u32)
2026 }
2027
2028 fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
2030 self.selection()
2031 .set_dom_start(start.map(Utf16CodeUnitLength::from))
2032 }
2033
2034 fn GetSelectionEnd(&self) -> Option<u32> {
2036 self.selection().dom_end().map(|end| end.0 as u32)
2037 }
2038
2039 fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
2041 self.selection()
2042 .set_dom_end(end.map(Utf16CodeUnitLength::from))
2043 }
2044
2045 fn GetSelectionDirection(&self) -> Option<DOMString> {
2047 self.selection().dom_direction()
2048 }
2049
2050 fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
2052 self.selection().set_dom_direction(direction)
2053 }
2054
2055 fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
2057 self.selection().set_dom_range(
2058 Utf16CodeUnitLength::from(start),
2059 Utf16CodeUnitLength::from(end),
2060 direction,
2061 )
2062 }
2063
2064 fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
2066 self.selection()
2067 .set_dom_range_text(replacement, None, None, Default::default())
2068 }
2069
2070 fn SetRangeText_(
2072 &self,
2073 replacement: DOMString,
2074 start: u32,
2075 end: u32,
2076 selection_mode: SelectionMode,
2077 ) -> ErrorResult {
2078 self.selection().set_dom_range_text(
2079 replacement,
2080 Some(Utf16CodeUnitLength::from(start)),
2081 Some(Utf16CodeUnitLength::from(end)),
2082 selection_mode,
2083 )
2084 }
2085
2086 fn SelectFiles(&self, paths: Vec<DOMString>) {
2089 if self.input_type() == InputType::File {
2090 self.select_files(Some(paths));
2091 }
2092 }
2093
2094 fn StepUp(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2096 self.step_up_or_down(n, StepDirection::Up, can_gc)
2097 }
2098
2099 fn StepDown(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2101 self.step_up_or_down(n, StepDirection::Down, can_gc)
2102 }
2103
2104 fn WillValidate(&self) -> bool {
2106 self.is_instance_validatable()
2107 }
2108
2109 fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
2111 self.validity_state(can_gc)
2112 }
2113
2114 fn CheckValidity(&self, can_gc: CanGc) -> bool {
2116 self.check_validity(can_gc)
2117 }
2118
2119 fn ReportValidity(&self, can_gc: CanGc) -> bool {
2121 self.report_validity(can_gc)
2122 }
2123
2124 fn ValidationMessage(&self) -> DOMString {
2126 self.validation_message()
2127 }
2128
2129 fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
2131 self.validity_state(can_gc).set_custom_error_message(error);
2132 }
2133}
2134
2135fn radio_group_iter<'a>(
2136 elem: &'a HTMLInputElement,
2137 group: Option<&'a Atom>,
2138 form: Option<&'a HTMLFormElement>,
2139 root: &'a Node,
2140) -> impl Iterator<Item = DomRoot<HTMLInputElement>> + 'a {
2141 root.traverse_preorder(ShadowIncluding::No)
2142 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2143 .filter(move |r| &**r == elem || in_same_group(r, form, group, Some(root)))
2144}
2145
2146fn broadcast_radio_checked(broadcaster: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2147 let root = broadcaster
2148 .upcast::<Node>()
2149 .GetRootNode(&GetRootNodeOptions::empty());
2150 let form = broadcaster.form_owner();
2151 for r in radio_group_iter(broadcaster, group, form.as_deref(), &root) {
2152 if broadcaster != &*r && r.Checked() {
2153 r.SetChecked(false, can_gc);
2154 }
2155 }
2156}
2157
2158fn perform_radio_group_validation(elem: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2159 let root = elem
2160 .upcast::<Node>()
2161 .GetRootNode(&GetRootNodeOptions::empty());
2162 let form = elem.form_owner();
2163 for r in radio_group_iter(elem, group, form.as_deref(), &root) {
2164 r.validity_state(can_gc)
2165 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2166 }
2167}
2168
2169fn in_same_group(
2171 other: &HTMLInputElement,
2172 owner: Option<&HTMLFormElement>,
2173 group: Option<&Atom>,
2174 tree_root: Option<&Node>,
2175) -> bool {
2176 if group.is_none() {
2177 return false;
2179 }
2180
2181 if other.input_type() != InputType::Radio ||
2182 other.form_owner().as_deref() != owner ||
2183 other.radio_group_name().as_ref() != group
2184 {
2185 return false;
2186 }
2187
2188 match tree_root {
2189 Some(tree_root) => {
2190 let other_root = other
2191 .upcast::<Node>()
2192 .GetRootNode(&GetRootNodeOptions::empty());
2193 tree_root == &*other_root
2194 },
2195 None => {
2196 true
2198 },
2199 }
2200}
2201
2202impl HTMLInputElement {
2203 fn radio_group_updated(&self, group: Option<&Atom>, can_gc: CanGc) {
2204 if self.Checked() {
2205 broadcast_radio_checked(self, group, can_gc);
2206 }
2207 }
2208
2209 pub(crate) fn form_datums(
2212 &self,
2213 submitter: Option<FormSubmitterElement>,
2214 encoding: Option<&'static Encoding>,
2215 ) -> Vec<FormDatum> {
2216 let ty = self.Type();
2220
2221 let name = self.Name();
2223 let is_submitter = match submitter {
2224 Some(FormSubmitterElement::Input(s)) => self == s,
2225 _ => false,
2226 };
2227
2228 match self.input_type() {
2229 InputType::Submit | InputType::Button | InputType::Reset if !is_submitter => {
2231 return vec![];
2232 },
2233
2234 InputType::Radio | InputType::Checkbox => {
2236 if !self.Checked() || name.is_empty() {
2237 return vec![];
2238 }
2239 },
2240
2241 InputType::File => {
2242 let mut datums = vec![];
2243
2244 let name = self.Name();
2246
2247 match self.GetFiles() {
2248 Some(fl) => {
2249 for f in fl.iter_files() {
2250 datums.push(FormDatum {
2251 ty: ty.clone(),
2252 name: name.clone(),
2253 value: FormDatumValue::File(DomRoot::from_ref(f)),
2254 });
2255 }
2256 },
2257 None => {
2258 datums.push(FormDatum {
2259 ty: ty.clone(),
2262 name: name.clone(),
2263 value: FormDatumValue::String(DOMString::from("")),
2264 })
2265 },
2266 }
2267
2268 return datums;
2269 },
2270
2271 InputType::Image => return vec![], InputType::Hidden => {
2275 if name.to_ascii_lowercase() == "_charset_" {
2276 return vec![FormDatum {
2277 ty: ty.clone(),
2278 name,
2279 value: FormDatumValue::String(match encoding {
2280 None => DOMString::from("UTF-8"),
2281 Some(enc) => DOMString::from(enc.name()),
2282 }),
2283 }];
2284 }
2285 },
2286
2287 _ => {
2289 if name.is_empty() {
2290 return vec![];
2291 }
2292 },
2293 }
2294
2295 vec![FormDatum {
2297 ty: ty.clone(),
2298 name,
2299 value: FormDatumValue::String(self.Value()),
2300 }]
2301 }
2302
2303 fn radio_group_name(&self) -> Option<Atom> {
2305 self.upcast::<Element>()
2306 .get_name()
2307 .filter(|name| !name.is_empty())
2308 }
2309
2310 fn update_checked_state(&self, checked: bool, dirty: bool, can_gc: CanGc) {
2311 self.upcast::<Element>()
2312 .set_state(ElementState::CHECKED, checked);
2313
2314 if dirty {
2315 self.checked_changed.set(true);
2316 }
2317
2318 if self.input_type() == InputType::Radio && checked {
2319 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
2320 }
2321
2322 self.upcast::<Node>().dirty(NodeDamage::Other);
2323 }
2324
2325 pub(crate) fn is_mutable(&self) -> bool {
2327 !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
2330 }
2331
2332 pub(crate) fn reset(&self, can_gc: CanGc) {
2334 match self.input_type() {
2335 InputType::Radio | InputType::Checkbox => {
2336 self.update_checked_state(self.DefaultChecked(), false, can_gc);
2337 self.checked_changed.set(false);
2338 self.value_changed(can_gc);
2339 },
2340 InputType::Image => (),
2341 _ => (),
2342 }
2343 self.textinput.borrow_mut().set_content(self.DefaultValue());
2344 self.value_dirty.set(false);
2345 self.upcast::<Node>().dirty(NodeDamage::Other);
2346 }
2347
2348 pub(crate) fn clear(&self, can_gc: CanGc) {
2351 self.value_dirty.set(false);
2353 self.checked_changed.set(false);
2354 self.textinput.borrow_mut().set_content(DOMString::from(""));
2356 self.update_checked_state(self.DefaultChecked(), false, can_gc);
2358 self.value_changed(can_gc);
2359 if self.filelist.get().is_some() {
2361 let window = self.owner_window();
2362 let filelist = FileList::new(&window, vec![], can_gc);
2363 self.filelist.set(Some(&filelist));
2364 }
2365 self.enable_sanitization();
2369 self.upcast::<Node>().dirty(NodeDamage::Other);
2370 }
2371
2372 fn update_placeholder_shown_state(&self) {
2373 if !self.input_type().is_textual_or_password() {
2374 self.upcast::<Element>().set_placeholder_shown_state(false);
2375 } else {
2376 let has_placeholder = !self.placeholder.borrow().is_empty();
2377 let has_value = !self.textinput.borrow().is_empty();
2378 self.upcast::<Element>()
2379 .set_placeholder_shown_state(has_placeholder && !has_value);
2380 }
2381 }
2382
2383 pub(crate) fn select_files_for_webdriver(
2384 &self,
2385 test_paths: Vec<DOMString>,
2386 response_sender: GenericSender<Result<bool, ErrorStatus>>,
2387 ) {
2388 let mut stored_sender = self.pending_webdriver_response.borrow_mut();
2389 assert!(stored_sender.is_none());
2390
2391 *stored_sender = Some(PendingWebDriverResponse {
2392 response_sender,
2393 expected_file_count: test_paths.len(),
2394 });
2395
2396 self.select_files(Some(test_paths));
2397 }
2398
2399 pub(crate) fn select_files(&self, test_paths: Option<Vec<DOMString>>) {
2403 let current_paths = match &test_paths {
2404 Some(test_paths) => test_paths
2405 .iter()
2406 .filter_map(|path_str| PathBuf::from_str(&path_str.str()).ok())
2407 .collect(),
2408 None => Default::default(),
2411 };
2412
2413 let accept_current_paths_for_testing = test_paths.is_some();
2414 self.owner_document()
2415 .embedder_controls()
2416 .show_embedder_control(
2417 ControlElement::FileInput(DomRoot::from_ref(self)),
2418 EmbedderControlRequest::FilePicker(FilePickerRequest {
2419 origin: get_blob_origin(&self.owner_window().get_url()),
2420 current_paths,
2421 filter_patterns: filter_from_accept(&self.Accept()),
2422 allow_select_multiple: self.Multiple(),
2423 accept_current_paths_for_testing,
2424 }),
2425 None,
2426 );
2427 }
2428
2429 fn sanitize_value(&self, value: &mut DOMString) {
2431 if !self.sanitization_flag.get() {
2436 return;
2437 }
2438 match self.input_type() {
2439 InputType::Text | InputType::Search | InputType::Tel | InputType::Password => {
2440 value.strip_newlines();
2441 },
2442 InputType::Url => {
2443 value.strip_newlines();
2444 value.strip_leading_and_trailing_ascii_whitespace();
2445 },
2446 InputType::Date => {
2447 if !value.str().is_valid_date_string() {
2448 value.clear();
2449 }
2450 },
2451 InputType::Month => {
2452 if !value.str().is_valid_month_string() {
2453 value.clear();
2454 }
2455 },
2456 InputType::Week => {
2457 if !value.str().is_valid_week_string() {
2458 value.clear();
2459 }
2460 },
2461 InputType::Color => {
2462 if value.str().is_valid_simple_color_string() {
2463 value.make_ascii_lowercase();
2464 } else {
2465 *value = "#000000".into();
2466 }
2467 },
2468 InputType::Time => {
2469 if !value.str().is_valid_time_string() {
2470 value.clear();
2471 }
2472 },
2473 InputType::DatetimeLocal => {
2474 let time = value
2475 .str()
2476 .parse_local_date_time_string()
2477 .map(|date_time| date_time.to_local_date_time_string());
2478 match time {
2479 Some(normalized_string) => *value = DOMString::from_string(normalized_string),
2480 None => value.clear(),
2481 }
2482 },
2483 InputType::Number => {
2484 if !value.is_valid_floating_point_number_string() {
2485 value.clear();
2486 }
2487 },
2494 InputType::Range => {
2496 if !value.is_valid_floating_point_number_string() {
2497 *value = DOMString::from(self.default_range_value().to_string());
2498 }
2499 if let Ok(fval) = &value.parse::<f64>() {
2500 let mut fval = *fval;
2501 if let Some(max) = self.maximum() {
2504 if fval > max {
2505 fval = max;
2506 }
2507 }
2508 if let Some(min) = self.minimum() {
2509 if fval < min {
2510 fval = min;
2511 }
2512 }
2513 if let Some(allowed_value_step) = self.allowed_value_step() {
2518 let step_base = self.step_base();
2519 let steps_from_base = (fval - step_base) / allowed_value_step;
2520 if steps_from_base.fract() != 0.0 {
2521 let int_steps = round_halves_positive(steps_from_base);
2524 fval = int_steps * allowed_value_step + step_base;
2526
2527 if let Some(stepped_maximum) = self.stepped_maximum() {
2531 if fval > stepped_maximum {
2532 fval = stepped_maximum;
2533 }
2534 }
2535 if let Some(stepped_minimum) = self.stepped_minimum() {
2536 if fval < stepped_minimum {
2537 fval = stepped_minimum;
2538 }
2539 }
2540 }
2541 }
2542 *value = DOMString::from(fval.to_string());
2543 };
2544 },
2545 InputType::Email => {
2546 if !self.Multiple() {
2547 value.strip_newlines();
2548 value.strip_leading_and_trailing_ascii_whitespace();
2549 } else {
2550 let sanitized = split_commas(&value.str())
2551 .map(|token| {
2552 let mut token = DOMString::from(token.to_string());
2553 token.strip_newlines();
2554 token.strip_leading_and_trailing_ascii_whitespace();
2555 token
2556 })
2557 .join(",");
2558 value.clear();
2559 value.push_str(sanitized.as_str());
2560 }
2561 },
2562 InputType::Button |
2565 InputType::Checkbox |
2566 InputType::File |
2567 InputType::Hidden |
2568 InputType::Image |
2569 InputType::Radio |
2570 InputType::Reset |
2571 InputType::Submit => (),
2572 }
2573 }
2574
2575 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
2576 fn selection(&self) -> TextControlSelection<'_, Self> {
2577 TextControlSelection::new(self, &self.textinput)
2578 }
2579
2580 fn implicit_submission(&self, can_gc: CanGc) {
2582 let doc = self.owner_document();
2583 let node = doc.upcast::<Node>();
2584 let owner = self.form_owner();
2585 let form = match owner {
2586 None => return,
2587 Some(ref f) => f,
2588 };
2589
2590 if self.upcast::<Element>().click_in_progress() {
2591 return;
2592 }
2593 let submit_button = node
2594 .query_selector_iter(DOMString::from("input[type=submit]"))
2595 .unwrap()
2596 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2597 .find(|r| r.form_owner() == owner);
2598 match submit_button {
2599 Some(ref button) => {
2600 if button.is_instance_activatable() {
2601 button
2604 .upcast::<Node>()
2605 .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
2606 }
2607 },
2608 None => {
2609 let mut inputs = node
2610 .query_selector_iter(DOMString::from("input"))
2611 .unwrap()
2612 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2613 .filter(|input| {
2614 input.form_owner() == owner &&
2615 matches!(
2616 input.input_type(),
2617 InputType::Text |
2618 InputType::Search |
2619 InputType::Url |
2620 InputType::Tel |
2621 InputType::Email |
2622 InputType::Password |
2623 InputType::Date |
2624 InputType::Month |
2625 InputType::Week |
2626 InputType::Time |
2627 InputType::DatetimeLocal |
2628 InputType::Number
2629 )
2630 });
2631
2632 if inputs.nth(1).is_some() {
2633 return;
2635 }
2636 form.submit(
2637 SubmittedFrom::NotFromForm,
2638 FormSubmitterElement::Form(form),
2639 can_gc,
2640 );
2641 },
2642 }
2643 }
2644
2645 fn convert_string_to_number(&self, value: &str) -> Option<f64> {
2647 match self.input_type() {
2648 InputType::Date => value.parse_date_string().map(|date_time| {
2655 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2656 }),
2657 InputType::Month => value.parse_month_string().map(|date_time| {
2666 ((date_time.year() - 1970) * 12) as f64 + (date_time.month() as u8 - 1) as f64
2667 }),
2668 InputType::Week => value.parse_week_string().map(|date_time| {
2675 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2676 }),
2677 InputType::Time => value
2682 .parse_time_string()
2683 .map(|date_time| (date_time.time() - Time::MIDNIGHT).whole_milliseconds() as f64),
2684 InputType::DatetimeLocal => value.parse_local_date_time_string().map(|date_time| {
2691 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2692 }),
2693 InputType::Number | InputType::Range => parse_floating_point_number(value),
2694 _ => None,
2697 }
2698 }
2699
2700 fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
2702 match self.input_type() {
2703 InputType::Date | InputType::Week | InputType::Time | InputType::DatetimeLocal => {
2704 OffsetDateTime::from_unix_timestamp_nanos((value * 1e6) as i128)
2705 .ok()
2706 .map(|value| self.convert_datetime_to_dom_string(value))
2707 },
2708 InputType::Month => {
2709 let date = OffsetDateTime::UNIX_EPOCH;
2713 let years = (value / 12.) as i32;
2714 let year = date.year() + years;
2715
2716 let months = value as i32 - (years * 12);
2717 let months = match months.cmp(&0) {
2718 Ordering::Less => (12 - months) as u8,
2719 Ordering::Equal | Ordering::Greater => months as u8,
2720 } + 1;
2721
2722 let date = date
2723 .replace_year(year)
2724 .ok()?
2725 .replace_month(Month::try_from(months).ok()?)
2726 .ok()?;
2727 Some(self.convert_datetime_to_dom_string(date))
2728 },
2729 InputType::Number | InputType::Range => {
2730 let mut value = DOMString::from(value.to_string());
2731 value.set_best_representation_of_the_floating_point_number();
2732 Some(value)
2733 },
2734 _ => unreachable!("Should not have called convert_number_to_string for non-Date types"),
2735 }
2736 }
2737
2738 fn convert_string_to_naive_datetime(&self, value: DOMString) -> Option<OffsetDateTime> {
2742 match self.input_type() {
2743 InputType::Date => value.str().parse_date_string(),
2744 InputType::Time => value.str().parse_time_string(),
2745 InputType::Week => value.str().parse_week_string(),
2746 InputType::Month => value.str().parse_month_string(),
2747 InputType::DatetimeLocal => value.str().parse_local_date_time_string(),
2748 _ => None,
2750 }
2751 }
2752
2753 fn convert_datetime_to_dom_string(&self, value: OffsetDateTime) -> DOMString {
2757 DOMString::from_string(match self.input_type() {
2758 InputType::Date => value.to_date_string(),
2759 InputType::Month => value.to_month_string(),
2760 InputType::Week => value.to_week_string(),
2761 InputType::Time => value.to_time_string(),
2762 InputType::DatetimeLocal => value.to_local_date_time_string(),
2763 _ => {
2764 unreachable!("Should not have called convert_datetime_to_string for non-Date types")
2765 },
2766 })
2767 }
2768
2769 fn update_related_validity_states(&self, can_gc: CanGc) {
2770 match self.input_type() {
2771 InputType::Radio => {
2772 perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc)
2773 },
2774 _ => {
2775 self.validity_state(can_gc)
2776 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2777 },
2778 }
2779 }
2780
2781 fn value_changed(&self, can_gc: CanGc) {
2782 self.update_related_validity_states(can_gc);
2783 self.get_or_create_shadow_tree(can_gc).update(self, can_gc);
2784 }
2785
2786 fn show_the_picker_if_applicable(&self) {
2788 if !self.is_mutable() {
2792 return;
2793 }
2794
2795 if self.input_type() == InputType::Color {
2798 let document = self.owner_document();
2799 let current_value = self.Value();
2800 let current_color = RgbColor {
2801 red: u8::from_str_radix(¤t_value.str()[1..3], 16).unwrap(),
2802 green: u8::from_str_radix(¤t_value.str()[3..5], 16).unwrap(),
2803 blue: u8::from_str_radix(¤t_value.str()[5..7], 16).unwrap(),
2804 };
2805 document.embedder_controls().show_embedder_control(
2806 ControlElement::ColorInput(DomRoot::from_ref(self)),
2807 EmbedderControlRequest::ColorPicker(current_color),
2808 None,
2809 );
2810 }
2811 }
2812
2813 pub(crate) fn handle_color_picker_response(&self, response: Option<RgbColor>, can_gc: CanGc) {
2814 let Some(selected_color) = response else {
2815 return;
2816 };
2817 let formatted_color = format!(
2818 "#{:0>2x}{:0>2x}{:0>2x}",
2819 selected_color.red, selected_color.green, selected_color.blue
2820 );
2821 let _ = self.SetValue(formatted_color.into(), can_gc);
2822 }
2823
2824 pub(crate) fn handle_file_picker_response(
2825 &self,
2826 response: Option<Vec<SelectedFile>>,
2827 can_gc: CanGc,
2828 ) {
2829 let mut files = Vec::new();
2830
2831 if let Some(pending_webdriver_reponse) = self.pending_webdriver_response.borrow_mut().take()
2832 {
2833 if self.Multiple() {
2840 if let Some(filelist) = self.filelist.get() {
2841 files = filelist.iter_files().map(|file| file.as_rooted()).collect();
2842 }
2843 }
2844
2845 let number_files_selected = response.as_ref().map(Vec::len).unwrap_or_default();
2846 pending_webdriver_reponse.finish(number_files_selected);
2847 }
2848
2849 let Some(response_files) = response else {
2850 return;
2851 };
2852
2853 let window = self.owner_window();
2854 files.extend(
2855 response_files
2856 .into_iter()
2857 .map(|file| File::new_from_selected(&window, file, can_gc)),
2858 );
2859
2860 if !self.Multiple() {
2863 files = files
2864 .pop()
2865 .map(|last_file| vec![last_file])
2866 .unwrap_or_default();
2867 }
2868
2869 self.filelist
2870 .set(Some(&FileList::new(&window, files, can_gc)));
2871
2872 let target = self.upcast::<EventTarget>();
2873 target.fire_event_with_params(
2874 atom!("input"),
2875 EventBubbles::Bubbles,
2876 EventCancelable::NotCancelable,
2877 EventComposed::Composed,
2878 can_gc,
2879 );
2880 target.fire_bubbling_event(atom!("change"), can_gc);
2881 }
2882
2883 fn handle_focus_event(&self, event: &FocusEvent) {
2884 if self.renders_as_text_input_widget() {
2888 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
2889 }
2890
2891 let event_type = event.upcast::<Event>().type_();
2892 if *event_type == *"blur" {
2893 self.owner_document()
2894 .embedder_controls()
2895 .hide_embedder_control(self.upcast());
2896 } else if *event_type == *"focus" {
2897 let Ok(input_method_type) = self.input_type().try_into() else {
2898 return;
2899 };
2900
2901 self.owner_document()
2902 .embedder_controls()
2903 .show_embedder_control(
2904 ControlElement::Ime(DomRoot::from_ref(self.upcast())),
2905 EmbedderControlRequest::InputMethod(InputMethodRequest {
2906 input_method_type,
2907 text: self.Value().to_string(),
2908 insertion_point: self.GetSelectionEnd(),
2909 multiline: false,
2910 }),
2911 None,
2912 );
2913 } else {
2914 unreachable!("Got unexpected FocusEvent {event_type:?}");
2915 }
2916 }
2917}
2918
2919impl VirtualMethods for HTMLInputElement {
2920 fn super_type(&self) -> Option<&dyn VirtualMethods> {
2921 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
2922 }
2923
2924 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
2925 let could_have_had_embedder_control = self.may_have_embedder_control();
2926
2927 self.super_type()
2928 .unwrap()
2929 .attribute_mutated(attr, mutation, can_gc);
2930
2931 match *attr.local_name() {
2932 local_name!("disabled") => {
2933 let disabled_state = match mutation {
2934 AttributeMutation::Set(None, _) => true,
2935 AttributeMutation::Set(Some(_), _) => {
2936 return;
2938 },
2939 AttributeMutation::Removed => false,
2940 };
2941 let el = self.upcast::<Element>();
2942 el.set_disabled_state(disabled_state);
2943 el.set_enabled_state(!disabled_state);
2944 el.check_ancestors_disabled_state_for_form_control();
2945
2946 if self.input_type().is_textual() {
2947 let read_write = !(self.ReadOnly() || el.disabled_state());
2948 el.set_read_write_state(read_write);
2949 }
2950
2951 el.update_sequentially_focusable_status(can_gc);
2952 },
2953 local_name!("checked") if !self.checked_changed.get() => {
2954 let checked_state = match mutation {
2955 AttributeMutation::Set(None, _) => true,
2956 AttributeMutation::Set(Some(_), _) => {
2957 return;
2959 },
2960 AttributeMutation::Removed => false,
2961 };
2962 self.update_checked_state(checked_state, false, can_gc);
2963 },
2964 local_name!("size") => {
2965 let size = mutation.new_value(attr).map(|value| value.as_uint());
2966 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
2967 },
2968 local_name!("type") => {
2969 let el = self.upcast::<Element>();
2970 match mutation {
2971 AttributeMutation::Set(..) => {
2972 let new_type = InputType::from(attr.value().as_atom());
2973
2974 let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
2976 let previously_selectable = self.selection_api_applies();
2977
2978 self.input_type.set(new_type);
2979
2980 if new_type.is_textual() {
2981 let read_write = !(self.ReadOnly() || el.disabled_state());
2982 el.set_read_write_state(read_write);
2983 } else {
2984 el.set_read_write_state(false);
2985 }
2986
2987 if new_type == InputType::File {
2988 let window = self.owner_window();
2989 let filelist = FileList::new(&window, vec![], can_gc);
2990 self.filelist.set(Some(&filelist));
2991 }
2992
2993 let new_value_mode = self.value_mode();
2994 match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
2995 (&ValueMode::Value, false, ValueMode::Default) |
2997 (&ValueMode::Value, false, ValueMode::DefaultOn) => {
2998 self.SetValue(old_idl_value, can_gc)
2999 .expect("Failed to set input value on type change to a default ValueMode.");
3000 },
3001
3002 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
3004 self.SetValue(
3005 self.upcast::<Element>()
3006 .get_attribute(&ns!(), &local_name!("value"))
3007 .map_or(DOMString::from(""), |a| {
3008 DOMString::from(a.summarize().value)
3009 }),
3010 can_gc,
3011 )
3012 .expect(
3013 "Failed to set input value on type change to ValueMode::Value.",
3014 );
3015 self.value_dirty.set(false);
3016 },
3017
3018 (_, _, ValueMode::Filename)
3020 if old_value_mode != ValueMode::Filename =>
3021 {
3022 self.SetValue(DOMString::from(""), can_gc)
3023 .expect("Failed to set input value on type change to ValueMode::Filename.");
3024 },
3025 _ => {},
3026 }
3027
3028 if new_type == InputType::Radio {
3030 self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3031 }
3032
3033 let mut textinput = self.textinput.borrow_mut();
3035 let mut value = textinput.get_content();
3036 self.sanitize_value(&mut value);
3037 textinput.set_content(value);
3038 self.upcast::<Node>().dirty(NodeDamage::Other);
3039
3040 if !previously_selectable && self.selection_api_applies() {
3042 textinput.clear_selection_to_start();
3043 }
3044 },
3045 AttributeMutation::Removed => {
3046 if self.input_type() == InputType::Radio {
3047 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
3048 }
3049 self.input_type.set(InputType::default());
3050 let el = self.upcast::<Element>();
3051
3052 let read_write = !(self.ReadOnly() || el.disabled_state());
3053 el.set_read_write_state(read_write);
3054 },
3055 }
3056
3057 self.update_placeholder_shown_state();
3058 self.get_or_create_shadow_tree(can_gc)
3059 .update_placeholder_contents(self, can_gc);
3060 },
3061 local_name!("value") if !self.value_dirty.get() => {
3064 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
3065 let mut value = value.map_or(DOMString::new(), DOMString::from);
3066
3067 self.sanitize_value(&mut value);
3068 self.textinput.borrow_mut().set_content(value);
3069 self.update_placeholder_shown_state();
3070
3071 self.upcast::<Node>().dirty(NodeDamage::Other);
3072 },
3073 local_name!("name") if self.input_type() == InputType::Radio => {
3074 self.radio_group_updated(
3075 mutation.new_value(attr).as_ref().map(|name| name.as_atom()),
3076 can_gc,
3077 );
3078 },
3079 local_name!("maxlength") => match *attr.value() {
3080 AttrValue::Int(_, value) => {
3081 let mut textinput = self.textinput.borrow_mut();
3082
3083 if value < 0 {
3084 textinput.set_max_length(None);
3085 } else {
3086 textinput.set_max_length(Some(Utf16CodeUnitLength(value as usize)))
3087 }
3088 },
3089 _ => panic!("Expected an AttrValue::Int"),
3090 },
3091 local_name!("minlength") => match *attr.value() {
3092 AttrValue::Int(_, value) => {
3093 let mut textinput = self.textinput.borrow_mut();
3094
3095 if value < 0 {
3096 textinput.set_min_length(None);
3097 } else {
3098 textinput.set_min_length(Some(Utf16CodeUnitLength(value as usize)))
3099 }
3100 },
3101 _ => panic!("Expected an AttrValue::Int"),
3102 },
3103 local_name!("placeholder") => {
3104 {
3105 let mut placeholder = self.placeholder.borrow_mut();
3106 placeholder.clear();
3107 if let AttributeMutation::Set(..) = mutation {
3108 placeholder
3109 .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
3110 }
3111 }
3112 self.update_placeholder_shown_state();
3113 self.get_or_create_shadow_tree(can_gc)
3114 .update_placeholder_contents(self, can_gc);
3115 },
3116 local_name!("readonly") => {
3117 if self.input_type().is_textual() {
3118 let el = self.upcast::<Element>();
3119 match mutation {
3120 AttributeMutation::Set(..) => {
3121 el.set_read_write_state(false);
3122 },
3123 AttributeMutation::Removed => {
3124 el.set_read_write_state(!el.disabled_state());
3125 },
3126 }
3127 }
3128 },
3129 local_name!("form") => {
3130 self.form_attribute_mutated(mutation, can_gc);
3131 },
3132 _ => {},
3133 }
3134
3135 self.value_changed(can_gc);
3136
3137 if could_have_had_embedder_control && !self.may_have_embedder_control() {
3138 self.owner_document()
3139 .embedder_controls()
3140 .hide_embedder_control(self.upcast());
3141 }
3142 }
3143
3144 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
3145 match *name {
3146 local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
3147 local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
3148 local_name!("type") => AttrValue::from_atomic(value.into()),
3149 local_name!("maxlength") => {
3150 AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
3151 },
3152 local_name!("minlength") => {
3153 AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
3154 },
3155 _ => self
3156 .super_type()
3157 .unwrap()
3158 .parse_plain_attribute(name, value),
3159 }
3160 }
3161
3162 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
3163 if let Some(s) = self.super_type() {
3164 s.bind_to_tree(context, can_gc);
3165 }
3166 self.upcast::<Element>()
3167 .check_ancestors_disabled_state_for_form_control();
3168
3169 if self.input_type() == InputType::Radio {
3170 self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3171 }
3172
3173 self.value_changed(can_gc);
3174 }
3175
3176 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
3177 let form_owner = self.form_owner();
3178
3179 self.super_type().unwrap().unbind_from_tree(context, can_gc);
3180
3181 let node = self.upcast::<Node>();
3182 let el = self.upcast::<Element>();
3183 if node
3184 .ancestors()
3185 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
3186 {
3187 el.check_ancestors_disabled_state_for_form_control();
3188 } else {
3189 el.check_disabled_attribute();
3190 }
3191
3192 if self.input_type() == InputType::Radio {
3193 let root = context.parent.GetRootNode(&GetRootNodeOptions::empty());
3194 for r in radio_group_iter(
3195 self,
3196 self.radio_group_name().as_ref(),
3197 form_owner.as_deref(),
3198 &root,
3199 ) {
3200 r.validity_state(can_gc)
3201 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3202 }
3203 }
3204
3205 self.validity_state(can_gc)
3206 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3207
3208 if self.input_type() == InputType::Color {
3209 self.owner_document()
3210 .embedder_controls()
3211 .hide_embedder_control(self.upcast());
3212 }
3213 }
3214
3215 fn handle_event(&self, event: &Event, can_gc: CanGc) {
3221 if let Some(s) = self.super_type() {
3222 s.handle_event(event, can_gc);
3223 }
3224
3225 if event.type_() == atom!("mousedown") && !event.DefaultPrevented() {
3226 if self.input_type().is_textual_or_password() &&
3229 !self.textinput.borrow().is_empty()
3231 {
3232 if let Some(grapheme_index) = self
3233 .owner_window()
3234 .text_index_query_on_node_for_event(self.upcast::<Node>(), event)
3235 {
3236 let mut textinput = self.textinput.borrow_mut();
3237 textinput.clear_selection_to_start();
3238 textinput.modify_edit_point(grapheme_index as isize, RopeMovement::Grapheme);
3239 } else {
3240 self.textinput.borrow_mut().clear_selection_to_end();
3241 }
3242 self.upcast::<Node>().dirty(NodeDamage::Other);
3243 }
3244 } else if event.type_() == atom!("keydown") &&
3245 !event.DefaultPrevented() &&
3246 self.input_type().is_textual_or_password()
3247 {
3248 if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
3249 let action = self.textinput.borrow_mut().handle_keydown(keyevent);
3252 self.handle_key_reaction(action, event, can_gc);
3253 }
3254 } else if event.type_() == atom!("keypress") &&
3255 !event.DefaultPrevented() &&
3256 self.input_type().is_textual_or_password()
3257 {
3258 } else if (event.type_() == atom!("compositionstart") ||
3262 event.type_() == atom!("compositionupdate") ||
3263 event.type_() == atom!("compositionend")) &&
3264 self.input_type().is_textual_or_password()
3265 {
3266 if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
3267 if event.type_() == atom!("compositionend") {
3268 let action = self
3269 .textinput
3270 .borrow_mut()
3271 .handle_compositionend(compositionevent);
3272 self.handle_key_reaction(action, event, can_gc);
3273 self.upcast::<Node>().dirty(NodeDamage::Other);
3274 self.update_placeholder_shown_state();
3275 } else if event.type_() == atom!("compositionupdate") {
3276 let action = self
3277 .textinput
3278 .borrow_mut()
3279 .handle_compositionupdate(compositionevent);
3280 self.handle_key_reaction(action, event, can_gc);
3281 self.upcast::<Node>().dirty(NodeDamage::Other);
3282 self.update_placeholder_shown_state();
3283 } else if event.type_() == atom!("compositionstart") {
3284 self.update_placeholder_shown_state();
3286 }
3287 event.mark_as_handled();
3288 }
3289 } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
3290 let reaction = self
3291 .textinput
3292 .borrow_mut()
3293 .handle_clipboard_event(clipboard_event);
3294 let flags = reaction.flags;
3295 if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
3296 self.owner_document().event_handler().fire_clipboard_event(
3297 None,
3298 ClipboardEventType::Change,
3299 can_gc,
3300 );
3301 }
3302 if flags.contains(ClipboardEventFlags::QueueInputEvent) {
3303 self.textinput.borrow().queue_input_event(
3304 self.upcast(),
3305 reaction.text,
3306 IsComposing::NotComposing,
3307 reaction.input_type,
3308 );
3309 }
3310 if !flags.is_empty() {
3311 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
3312 }
3313 } else if let Some(event) = event.downcast::<FocusEvent>() {
3314 self.handle_focus_event(event)
3315 }
3316
3317 self.value_changed(can_gc);
3318 }
3319
3320 fn cloning_steps(
3322 &self,
3323 copy: &Node,
3324 maybe_doc: Option<&Document>,
3325 clone_children: CloneChildrenFlag,
3326 can_gc: CanGc,
3327 ) {
3328 if let Some(s) = self.super_type() {
3329 s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
3330 }
3331 let elem = copy.downcast::<HTMLInputElement>().unwrap();
3332 elem.value_dirty.set(self.value_dirty.get());
3333 elem.checked_changed.set(self.checked_changed.get());
3334 elem.upcast::<Element>()
3335 .set_state(ElementState::CHECKED, self.Checked());
3336 elem.textinput
3337 .borrow_mut()
3338 .set_content(self.textinput.borrow().get_content());
3339 self.value_changed(can_gc);
3340 }
3341}
3342
3343impl FormControl for HTMLInputElement {
3344 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
3345 self.form_owner.get()
3346 }
3347
3348 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
3349 self.form_owner.set(form);
3350 }
3351
3352 fn to_element(&self) -> &Element {
3353 self.upcast::<Element>()
3354 }
3355}
3356
3357impl Validatable for HTMLInputElement {
3358 fn as_element(&self) -> &Element {
3359 self.upcast()
3360 }
3361
3362 fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
3363 self.validity_state
3364 .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
3365 }
3366
3367 fn is_instance_validatable(&self) -> bool {
3368 match self.input_type() {
3375 InputType::Hidden | InputType::Button | InputType::Reset => false,
3376 _ => {
3377 !(self.upcast::<Element>().disabled_state() ||
3378 self.ReadOnly() ||
3379 is_barred_by_datalist_ancestor(self.upcast()))
3380 },
3381 }
3382 }
3383
3384 fn perform_validation(
3385 &self,
3386 validate_flags: ValidationFlags,
3387 can_gc: CanGc,
3388 ) -> ValidationFlags {
3389 let mut failed_flags = ValidationFlags::empty();
3390 let value = self.Value();
3391
3392 if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
3393 self.suffers_from_being_missing(&value)
3394 {
3395 failed_flags.insert(ValidationFlags::VALUE_MISSING);
3396 }
3397
3398 if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
3399 self.suffers_from_type_mismatch(&value)
3400 {
3401 failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
3402 }
3403
3404 if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
3405 self.suffers_from_pattern_mismatch(&value, can_gc)
3406 {
3407 failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
3408 }
3409
3410 if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
3411 self.suffers_from_bad_input(&value)
3412 {
3413 failed_flags.insert(ValidationFlags::BAD_INPUT);
3414 }
3415
3416 if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
3417 failed_flags |= self.suffers_from_length_issues(&value);
3418 }
3419
3420 if validate_flags.intersects(
3421 ValidationFlags::RANGE_UNDERFLOW |
3422 ValidationFlags::RANGE_OVERFLOW |
3423 ValidationFlags::STEP_MISMATCH,
3424 ) {
3425 failed_flags |= self.suffers_from_range_issues(&value);
3426 }
3427
3428 failed_flags & validate_flags
3429 }
3430}
3431
3432impl Activatable for HTMLInputElement {
3433 fn as_element(&self) -> &Element {
3434 self.upcast()
3435 }
3436
3437 fn is_instance_activatable(&self) -> bool {
3438 match self.input_type() {
3439 InputType::Submit | InputType::Reset | InputType::File | InputType::Image => {
3444 self.is_mutable()
3445 },
3446 InputType::Checkbox | InputType::Radio | InputType::Color => true,
3450 _ => false,
3451 }
3452 }
3453
3454 fn legacy_pre_activation_behavior(&self, can_gc: CanGc) -> Option<InputActivationState> {
3456 let ty = self.input_type();
3457 let activation_state = match ty {
3458 InputType::Checkbox => {
3459 let was_checked = self.Checked();
3460 let was_indeterminate = self.Indeterminate();
3461 self.SetIndeterminate(false);
3462 self.SetChecked(!was_checked, can_gc);
3463 Some(InputActivationState {
3464 checked: was_checked,
3465 indeterminate: was_indeterminate,
3466 checked_radio: None,
3467 old_type: InputType::Checkbox,
3468 })
3469 },
3470 InputType::Radio => {
3471 let root = self
3472 .upcast::<Node>()
3473 .GetRootNode(&GetRootNodeOptions::empty());
3474 let form_owner = self.form_owner();
3475 let checked_member = radio_group_iter(
3476 self,
3477 self.radio_group_name().as_ref(),
3478 form_owner.as_deref(),
3479 &root,
3480 )
3481 .find(|r| r.Checked());
3482 let was_checked = self.Checked();
3483 self.SetChecked(true, can_gc);
3484 Some(InputActivationState {
3485 checked: was_checked,
3486 indeterminate: false,
3487 checked_radio: checked_member.as_deref().map(DomRoot::from_ref),
3488 old_type: InputType::Radio,
3489 })
3490 },
3491 _ => None,
3492 };
3493
3494 if activation_state.is_some() {
3495 self.value_changed(can_gc);
3496 }
3497
3498 activation_state
3499 }
3500
3501 fn legacy_canceled_activation_behavior(
3503 &self,
3504 cache: Option<InputActivationState>,
3505 can_gc: CanGc,
3506 ) {
3507 let ty = self.input_type();
3509 let cache = match cache {
3510 Some(cache) => {
3511 if cache.old_type != ty {
3512 return;
3515 }
3516 cache
3517 },
3518 None => {
3519 return;
3520 },
3521 };
3522
3523 match ty {
3524 InputType::Checkbox => {
3526 self.SetIndeterminate(cache.indeterminate);
3527 self.SetChecked(cache.checked, can_gc);
3528 },
3529 InputType::Radio => {
3531 if let Some(ref o) = cache.checked_radio {
3532 let tree_root = self
3533 .upcast::<Node>()
3534 .GetRootNode(&GetRootNodeOptions::empty());
3535 if in_same_group(
3538 o,
3539 self.form_owner().as_deref(),
3540 self.radio_group_name().as_ref(),
3541 Some(&*tree_root),
3542 ) {
3543 o.SetChecked(true, can_gc);
3544 } else {
3545 self.SetChecked(false, can_gc);
3546 }
3547 } else {
3548 self.SetChecked(false, can_gc);
3549 }
3550 },
3551 _ => (),
3552 }
3553
3554 self.value_changed(can_gc);
3555 }
3556
3557 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) {
3559 match self.input_type() {
3560 InputType::Submit | InputType::Image => {
3563 if let Some(form_owner) = self.form_owner() {
3565 let document = self.owner_document();
3567
3568 if !document.is_fully_active() {
3569 return;
3570 }
3571
3572 form_owner.submit(
3575 SubmittedFrom::NotFromForm,
3576 FormSubmitterElement::Input(self),
3577 can_gc,
3578 )
3579 }
3580 },
3581 InputType::Reset => {
3582 if let Some(form_owner) = self.form_owner() {
3585 let document = self.owner_document();
3586
3587 if !document.is_fully_active() {
3589 return;
3590 }
3591
3592 form_owner.reset(ResetFrom::NotFromForm, can_gc);
3594 }
3595 },
3596 InputType::Checkbox | InputType::Radio => {
3599 if !self.upcast::<Node>().is_connected() {
3601 return;
3602 }
3603
3604 let target = self.upcast::<EventTarget>();
3605
3606 target.fire_event_with_params(
3609 atom!("input"),
3610 EventBubbles::Bubbles,
3611 EventCancelable::NotCancelable,
3612 EventComposed::Composed,
3613 can_gc,
3614 );
3615
3616 target.fire_bubbling_event(atom!("change"), can_gc);
3619 },
3620 InputType::File => {
3622 self.select_files(None);
3623 },
3624 InputType::Color => {
3626 self.show_the_picker_if_applicable();
3627 },
3628 _ => (),
3629 }
3630 }
3631}
3632
3633fn filter_from_accept(s: &DOMString) -> Vec<FilterPattern> {
3635 let mut filter = vec![];
3636 for p in split_commas(&s.str()) {
3637 let p = p.trim();
3638 if let Some('.') = p.chars().next() {
3639 filter.push(FilterPattern(p[1..].to_string()));
3640 } else if let Some(exts) = mime_guess::get_mime_extensions_str(p) {
3641 for ext in exts {
3642 filter.push(FilterPattern(ext.to_string()));
3643 }
3644 }
3645 }
3646
3647 filter
3648}
3649
3650fn round_halves_positive(n: f64) -> f64 {
3651 if n.fract() == -0.5 {
3655 n.ceil()
3656 } else {
3657 n.round()
3658 }
3659}
3660
3661fn compile_pattern(
3665 cx: SafeJSContext,
3666 pattern_str: &str,
3667 out_regex: MutableHandleObject,
3668 can_gc: CanGc,
3669) -> bool {
3670 if check_js_regex_syntax(cx, pattern_str, can_gc) {
3672 let pattern_str = format!("^(?:{})$", pattern_str);
3674 let flags = RegExpFlags {
3675 flags_: RegExpFlag_UnicodeSets,
3676 };
3677 new_js_regex(cx, &pattern_str, flags, out_regex, can_gc)
3678 } else {
3679 false
3680 }
3681}
3682
3683#[expect(unsafe_code)]
3684fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str, _can_gc: CanGc) -> bool {
3687 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3688 unsafe {
3689 rooted!(in(*cx) let mut exception = UndefinedValue());
3690
3691 let valid = CheckRegExpSyntax(
3692 *cx,
3693 pattern.as_ptr(),
3694 pattern.len(),
3695 RegExpFlags {
3696 flags_: RegExpFlag_UnicodeSets,
3697 },
3698 exception.handle_mut(),
3699 );
3700
3701 if !valid {
3702 JS_ClearPendingException(*cx);
3703 return false;
3704 }
3705
3706 exception.is_undefined()
3709 }
3710}
3711
3712#[expect(unsafe_code)]
3713pub(crate) fn new_js_regex(
3714 cx: SafeJSContext,
3715 pattern: &str,
3716 flags: RegExpFlags,
3717 mut out_regex: MutableHandleObject,
3718 _can_gc: CanGc,
3719) -> bool {
3720 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3721 unsafe {
3722 out_regex.set(NewUCRegExpObject(
3723 *cx,
3724 pattern.as_ptr(),
3725 pattern.len(),
3726 flags,
3727 ));
3728 if out_regex.is_null() {
3729 JS_ClearPendingException(*cx);
3730 return false;
3731 }
3732 }
3733 true
3734}
3735
3736#[expect(unsafe_code)]
3737fn matches_js_regex(
3738 cx: SafeJSContext,
3739 regex_obj: HandleObject,
3740 value: &str,
3741 _can_gc: CanGc,
3742) -> Result<bool, ()> {
3743 let mut value: Vec<u16> = value.encode_utf16().collect();
3744
3745 unsafe {
3746 let mut is_regex = false;
3747 assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
3748 assert!(is_regex);
3749
3750 rooted!(in(*cx) let mut rval = UndefinedValue());
3751 let mut index = 0;
3752
3753 let ok = ExecuteRegExpNoStatics(
3754 *cx,
3755 regex_obj,
3756 value.as_mut_ptr(),
3757 value.len(),
3758 &mut index,
3759 true,
3760 rval.handle_mut(),
3761 );
3762
3763 if ok {
3764 Ok(!rval.is_null())
3765 } else {
3766 JS_ClearPendingException(*cx);
3767 Err(())
3768 }
3769 }
3770}
3771
3772#[derive(MallocSizeOf)]
3776struct PendingWebDriverResponse {
3777 response_sender: GenericSender<Result<bool, ErrorStatus>>,
3779 expected_file_count: usize,
3781}
3782
3783impl PendingWebDriverResponse {
3784 fn finish(self, number_files_selected: usize) {
3785 if number_files_selected == self.expected_file_count {
3786 let _ = self.response_sender.send(Ok(false));
3787 } else {
3788 let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
3791 }
3792 }
3793}