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