1use std::borrow::Cow;
6use std::cell::{Cell, RefCell};
7use std::cmp::Ordering;
8use std::ops::Range;
9use std::path::PathBuf;
10use std::ptr::NonNull;
11use std::str::FromStr;
12use std::{f64, ptr};
13
14use dom_struct::dom_struct;
15use embedder_traits::{
16 EmbedderControlRequest, FilePickerRequest, FilterPattern, InputMethodRequest, InputMethodType,
17 RgbColor, SelectedFile,
18};
19use encoding_rs::Encoding;
20use html5ever::{LocalName, Prefix, QualName, local_name, ns};
21use ipc_channel::ipc::IpcSender;
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 net_traits::blob_url_store::get_blob_origin;
31use script_bindings::codegen::GenericBindings::CharacterDataBinding::CharacterDataMethods;
32use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
33use script_bindings::domstring::parse_floating_point_number;
34use style::attr::AttrValue;
35use style::selector_parser::PseudoElement;
36use style::str::split_commas;
37use stylo_atoms::Atom;
38use stylo_dom::ElementState;
39use time::{Month, OffsetDateTime, Time};
40use unicode_bidi::{BidiClass, bidi_class};
41use url::Url;
42use webdriver::error::ErrorStatus;
43
44use crate::clipboard_provider::EmbedderClipboardProvider;
45use crate::dom::activation::Activatable;
46use crate::dom::attr::Attr;
47use crate::dom::bindings::cell::{DomRefCell, Ref};
48use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
49use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
50use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
51use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
52use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
53use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
54use crate::dom::bindings::error::{Error, ErrorResult};
55use crate::dom::bindings::inheritance::Castable;
56use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
57use crate::dom::bindings::str::{DOMString, FromInputValueString, ToInputValueString, USVString};
58use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType};
59use crate::dom::compositionevent::CompositionEvent;
60use crate::dom::document::Document;
61use crate::dom::document_embedder_controls::ControlElement;
62use crate::dom::element::{
63 AttributeMutation, CustomElementCreationMode, Element, ElementCreator, LayoutElementHelpers,
64};
65use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
66use crate::dom::eventtarget::EventTarget;
67use crate::dom::file::File;
68use crate::dom::filelist::{FileList, LayoutFileListHelpers};
69use crate::dom::globalscope::GlobalScope;
70use crate::dom::html::htmldatalistelement::HTMLDataListElement;
71use crate::dom::html::htmlelement::HTMLElement;
72use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
73use crate::dom::html::htmlformelement::{
74 FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom,
75 SubmittedFrom,
76};
77use crate::dom::keyboardevent::KeyboardEvent;
78use crate::dom::mouseevent::MouseEvent;
79use crate::dom::node::{
80 BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext,
81};
82use crate::dom::nodelist::NodeList;
83use crate::dom::shadowroot::ShadowRoot;
84use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
85use crate::dom::types::{CharacterData, FocusEvent};
86use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
87use crate::dom::validitystate::{ValidationFlags, ValidityState};
88use crate::dom::virtualmethods::VirtualMethods;
89use crate::realms::enter_realm;
90use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
91use crate::textinput::Lines::Single;
92use crate::textinput::{
93 ClipboardEventFlags, Direction, IsComposing, KeyReaction, SelectionDirection, TextInput,
94 UTF8Bytes, UTF16CodeUnits,
95};
96
97const DEFAULT_SUBMIT_VALUE: &str = "Submit";
98const DEFAULT_RESET_VALUE: &str = "Reset";
99const PASSWORD_REPLACEMENT_CHAR: char = '●';
100const DEFAULT_FILE_INPUT_VALUE: &str = "No file chosen";
101
102#[derive(Clone, JSTraceable, MallocSizeOf)]
103#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
104struct InputTypeTextShadowTree {
124 inner_container: Dom<Element>,
125 text_container: Dom<Element>,
126 placeholder_container: DomRefCell<Option<Dom<Element>>>,
127}
128
129impl InputTypeTextShadowTree {
130 fn init_placeholder_container_if_necessary(&self, host: &HTMLInputElement, can_gc: CanGc) {
133 if self.placeholder_container.borrow().is_some() || host.placeholder.borrow().is_empty() {
136 return;
137 }
138
139 *self.placeholder_container.borrow_mut() = Some(
140 create_ua_widget_div_with_text_node(
141 &host.owner_document(),
142 self.inner_container.upcast::<Node>(),
143 PseudoElement::Placeholder,
144 true,
145 can_gc,
146 )
147 .as_traced(),
148 );
149 }
150}
151
152#[derive(Clone, JSTraceable, MallocSizeOf)]
153#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
154struct InputTypeColorShadowTree {
159 color_value: Dom<Element>,
160}
161
162#[derive(Clone, JSTraceable, MallocSizeOf)]
163#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
164#[non_exhaustive]
165enum ShadowTree {
166 Text(InputTypeTextShadowTree),
167 Color(InputTypeColorShadowTree),
168 }
170
171fn create_ua_widget_div_with_text_node(
174 document: &Document,
175 parent: &Node,
176 implemented_pseudo: PseudoElement,
177 as_first_child: bool,
178 can_gc: CanGc,
179) -> DomRoot<Element> {
180 let el = Element::create(
181 QualName::new(None, ns!(html), local_name!("div")),
182 None,
183 document,
184 ElementCreator::ScriptCreated,
185 CustomElementCreationMode::Asynchronous,
186 None,
187 can_gc,
188 );
189
190 parent
191 .upcast::<Node>()
192 .AppendChild(el.upcast::<Node>(), can_gc)
193 .unwrap();
194 el.upcast::<Node>()
195 .set_implemented_pseudo_element(implemented_pseudo);
196 let text_node = document.CreateTextNode("".into(), can_gc);
197
198 if !as_first_child {
199 el.upcast::<Node>()
200 .AppendChild(text_node.upcast::<Node>(), can_gc)
201 .unwrap();
202 } else {
203 el.upcast::<Node>()
204 .InsertBefore(
205 text_node.upcast::<Node>(),
206 el.upcast::<Node>().GetFirstChild().as_deref(),
207 can_gc,
208 )
209 .unwrap();
210 }
211 el
212}
213
214#[derive(Clone, Copy, Debug, Default, JSTraceable, PartialEq, MallocSizeOf)]
216pub(crate) enum InputType {
217 Button,
219
220 Checkbox,
222
223 Color,
225
226 Date,
228
229 DatetimeLocal,
231
232 Email,
234
235 File,
237
238 Hidden,
240
241 Image,
243
244 Month,
246
247 Number,
249
250 Password,
252
253 Radio,
255
256 Range,
258
259 Reset,
261
262 Search,
264
265 Submit,
267
268 Tel,
270
271 #[default]
273 Text,
274
275 Time,
277
278 Url,
280
281 Week,
283}
284
285impl InputType {
286 pub(crate) fn is_textual(&self) -> bool {
291 matches!(
292 *self,
293 InputType::Date |
294 InputType::DatetimeLocal |
295 InputType::Email |
296 InputType::Hidden |
297 InputType::Month |
298 InputType::Number |
299 InputType::Range |
300 InputType::Search |
301 InputType::Tel |
302 InputType::Text |
303 InputType::Time |
304 InputType::Url |
305 InputType::Week
306 )
307 }
308
309 fn is_textual_or_password(&self) -> bool {
310 self.is_textual() || *self == InputType::Password
311 }
312
313 fn has_periodic_domain(&self) -> bool {
315 *self == InputType::Time
316 }
317
318 fn as_str(&self) -> &str {
319 match *self {
320 InputType::Button => "button",
321 InputType::Checkbox => "checkbox",
322 InputType::Color => "color",
323 InputType::Date => "date",
324 InputType::DatetimeLocal => "datetime-local",
325 InputType::Email => "email",
326 InputType::File => "file",
327 InputType::Hidden => "hidden",
328 InputType::Image => "image",
329 InputType::Month => "month",
330 InputType::Number => "number",
331 InputType::Password => "password",
332 InputType::Radio => "radio",
333 InputType::Range => "range",
334 InputType::Reset => "reset",
335 InputType::Search => "search",
336 InputType::Submit => "submit",
337 InputType::Tel => "tel",
338 InputType::Text => "text",
339 InputType::Time => "time",
340 InputType::Url => "url",
341 InputType::Week => "week",
342 }
343 }
344}
345
346impl TryFrom<InputType> for InputMethodType {
347 type Error = &'static str;
348
349 fn try_from(input_type: InputType) -> Result<Self, Self::Error> {
350 match input_type {
351 InputType::Color => Ok(InputMethodType::Color),
352 InputType::Date => Ok(InputMethodType::Date),
353 InputType::DatetimeLocal => Ok(InputMethodType::DatetimeLocal),
354 InputType::Email => Ok(InputMethodType::Email),
355 InputType::Month => Ok(InputMethodType::Month),
356 InputType::Number => Ok(InputMethodType::Number),
357 InputType::Password => Ok(InputMethodType::Password),
358 InputType::Search => Ok(InputMethodType::Search),
359 InputType::Tel => Ok(InputMethodType::Tel),
360 InputType::Text => Ok(InputMethodType::Text),
361 InputType::Time => Ok(InputMethodType::Time),
362 InputType::Url => Ok(InputMethodType::Url),
363 InputType::Week => Ok(InputMethodType::Week),
364 _ => Err("Input does not support IME."),
365 }
366 }
367}
368
369impl From<&Atom> for InputType {
370 fn from(value: &Atom) -> InputType {
371 match value.to_ascii_lowercase() {
372 atom!("button") => InputType::Button,
373 atom!("checkbox") => InputType::Checkbox,
374 atom!("color") => InputType::Color,
375 atom!("date") => InputType::Date,
376 atom!("datetime-local") => InputType::DatetimeLocal,
377 atom!("email") => InputType::Email,
378 atom!("file") => InputType::File,
379 atom!("hidden") => InputType::Hidden,
380 atom!("image") => InputType::Image,
381 atom!("month") => InputType::Month,
382 atom!("number") => InputType::Number,
383 atom!("password") => InputType::Password,
384 atom!("radio") => InputType::Radio,
385 atom!("range") => InputType::Range,
386 atom!("reset") => InputType::Reset,
387 atom!("search") => InputType::Search,
388 atom!("submit") => InputType::Submit,
389 atom!("tel") => InputType::Tel,
390 atom!("text") => InputType::Text,
391 atom!("time") => InputType::Time,
392 atom!("url") => InputType::Url,
393 atom!("week") => InputType::Week,
394 _ => Self::default(),
395 }
396 }
397}
398
399#[derive(Debug, PartialEq)]
400enum ValueMode {
401 Value,
403
404 Default,
406
407 DefaultOn,
409
410 Filename,
412}
413
414#[derive(Debug, PartialEq)]
415enum StepDirection {
416 Up,
417 Down,
418}
419
420#[dom_struct]
421pub(crate) struct HTMLInputElement {
422 htmlelement: HTMLElement,
423 input_type: Cell<InputType>,
424
425 checked_changed: Cell<bool>,
427 placeholder: DomRefCell<DOMString>,
428 size: Cell<u32>,
429 maxlength: Cell<i32>,
430 minlength: Cell<i32>,
431 #[no_trace]
432 textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
433 value_dirty: Cell<bool>,
435 sanitization_flag: Cell<bool>,
439
440 filelist: MutNullableDom<FileList>,
441 form_owner: MutNullableDom<HTMLFormElement>,
442 labels_node_list: MutNullableDom<NodeList>,
443 validity_state: MutNullableDom<ValidityState>,
444 shadow_tree: DomRefCell<Option<ShadowTree>>,
445 #[no_trace]
446 pending_webdriver_response: RefCell<Option<PendingWebDriverResponse>>,
447}
448
449#[derive(JSTraceable)]
450pub(crate) struct InputActivationState {
451 indeterminate: bool,
452 checked: bool,
453 checked_radio: Option<DomRoot<HTMLInputElement>>,
454 old_type: InputType,
456 }
458
459static DEFAULT_INPUT_SIZE: u32 = 20;
460static DEFAULT_MAX_LENGTH: i32 = -1;
461static DEFAULT_MIN_LENGTH: i32 = -1;
462
463#[allow(non_snake_case)]
464impl HTMLInputElement {
465 fn new_inherited(
466 local_name: LocalName,
467 prefix: Option<Prefix>,
468 document: &Document,
469 ) -> HTMLInputElement {
470 let embedder_sender = document
471 .window()
472 .as_global_scope()
473 .script_to_embedder_chan()
474 .clone();
475 HTMLInputElement {
476 htmlelement: HTMLElement::new_inherited_with_state(
477 ElementState::ENABLED | ElementState::READWRITE,
478 local_name,
479 prefix,
480 document,
481 ),
482 input_type: Cell::new(Default::default()),
483 placeholder: DomRefCell::new(DOMString::new()),
484 checked_changed: Cell::new(false),
485 maxlength: Cell::new(DEFAULT_MAX_LENGTH),
486 minlength: Cell::new(DEFAULT_MIN_LENGTH),
487 size: Cell::new(DEFAULT_INPUT_SIZE),
488 textinput: DomRefCell::new(TextInput::new(
489 Single,
490 DOMString::new(),
491 EmbedderClipboardProvider {
492 embedder_sender,
493 webview_id: document.webview_id(),
494 },
495 None,
496 None,
497 SelectionDirection::None,
498 )),
499 value_dirty: Cell::new(false),
500 sanitization_flag: Cell::new(true),
501 filelist: MutNullableDom::new(None),
502 form_owner: Default::default(),
503 labels_node_list: MutNullableDom::new(None),
504 validity_state: Default::default(),
505 shadow_tree: Default::default(),
506 pending_webdriver_response: Default::default(),
507 }
508 }
509
510 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
511 pub(crate) fn new(
512 local_name: LocalName,
513 prefix: Option<Prefix>,
514 document: &Document,
515 proto: Option<HandleObject>,
516 can_gc: CanGc,
517 ) -> DomRoot<HTMLInputElement> {
518 Node::reflect_node_with_proto(
519 Box::new(HTMLInputElement::new_inherited(
520 local_name, prefix, document,
521 )),
522 document,
523 proto,
524 can_gc,
525 )
526 }
527
528 pub(crate) fn auto_directionality(&self) -> Option<String> {
529 match self.input_type() {
530 InputType::Text | InputType::Search | InputType::Url | InputType::Email => {
531 let value: String = self.Value().to_string();
532 Some(HTMLInputElement::directionality_from_value(&value))
533 },
534 _ => None,
535 }
536 }
537
538 pub(crate) fn directionality_from_value(value: &str) -> String {
539 if HTMLInputElement::is_first_strong_character_rtl(value) {
540 "rtl".to_owned()
541 } else {
542 "ltr".to_owned()
543 }
544 }
545
546 fn is_first_strong_character_rtl(value: &str) -> bool {
547 for ch in value.chars() {
548 return match bidi_class(ch) {
549 BidiClass::L => false,
550 BidiClass::AL => true,
551 BidiClass::R => true,
552 _ => continue,
553 };
554 }
555 false
556 }
557
558 fn value_mode(&self) -> ValueMode {
561 match self.input_type() {
562 InputType::Submit |
563 InputType::Reset |
564 InputType::Button |
565 InputType::Image |
566 InputType::Hidden => ValueMode::Default,
567
568 InputType::Checkbox | InputType::Radio => ValueMode::DefaultOn,
569
570 InputType::Color |
571 InputType::Date |
572 InputType::DatetimeLocal |
573 InputType::Email |
574 InputType::Month |
575 InputType::Number |
576 InputType::Password |
577 InputType::Range |
578 InputType::Search |
579 InputType::Tel |
580 InputType::Text |
581 InputType::Time |
582 InputType::Url |
583 InputType::Week => ValueMode::Value,
584
585 InputType::File => ValueMode::Filename,
586 }
587 }
588
589 #[inline]
590 pub(crate) fn input_type(&self) -> InputType {
591 self.input_type.get()
592 }
593
594 pub(crate) fn is_nontypeable(&self) -> bool {
596 matches!(
597 self.input_type(),
598 InputType::Button |
599 InputType::Checkbox |
600 InputType::Color |
601 InputType::File |
602 InputType::Hidden |
603 InputType::Image |
604 InputType::Radio |
605 InputType::Range |
606 InputType::Reset |
607 InputType::Submit
608 )
609 }
610
611 #[inline]
612 pub(crate) fn is_submit_button(&self) -> bool {
613 let input_type = self.input_type.get();
614 input_type == InputType::Submit || input_type == InputType::Image
615 }
616
617 pub(crate) fn disable_sanitization(&self) {
618 self.sanitization_flag.set(false);
619 }
620
621 pub(crate) fn enable_sanitization(&self) {
622 self.sanitization_flag.set(true);
623 let mut textinput = self.textinput.borrow_mut();
624 let mut value = textinput.single_line_content().clone();
625 self.sanitize_value(&mut value);
626 textinput.set_content(value);
627 self.upcast::<Node>().dirty(NodeDamage::Other);
628 }
629
630 fn does_minmaxlength_apply(&self) -> bool {
631 matches!(
632 self.input_type(),
633 InputType::Text |
634 InputType::Search |
635 InputType::Url |
636 InputType::Tel |
637 InputType::Email |
638 InputType::Password
639 )
640 }
641
642 fn does_pattern_apply(&self) -> bool {
643 matches!(
644 self.input_type(),
645 InputType::Text |
646 InputType::Search |
647 InputType::Url |
648 InputType::Tel |
649 InputType::Email |
650 InputType::Password
651 )
652 }
653
654 fn does_multiple_apply(&self) -> bool {
655 self.input_type() == InputType::Email
656 }
657
658 fn does_value_as_number_apply(&self) -> bool {
661 matches!(
662 self.input_type(),
663 InputType::Date |
664 InputType::Month |
665 InputType::Week |
666 InputType::Time |
667 InputType::DatetimeLocal |
668 InputType::Number |
669 InputType::Range
670 )
671 }
672
673 fn does_value_as_date_apply(&self) -> bool {
674 matches!(
675 self.input_type(),
676 InputType::Date | InputType::Month | InputType::Week | InputType::Time
677 )
678 }
679
680 fn allowed_value_step(&self) -> Option<f64> {
682 let default_step = self.default_step()?;
685
686 let Some(attribute) = self
689 .upcast::<Element>()
690 .get_attribute(&ns!(), &local_name!("step"))
691 else {
692 return Some(default_step * self.step_scale_factor());
693 };
694
695 if attribute.value().eq_ignore_ascii_case("any") {
698 return None;
699 }
700
701 let Some(parsed_value) =
705 parse_floating_point_number(&attribute.value()).filter(|value| *value > 0.0)
706 else {
707 return Some(default_step * self.step_scale_factor());
708 };
709
710 Some(parsed_value * self.step_scale_factor())
714 }
715
716 fn minimum(&self) -> Option<f64> {
718 self.upcast::<Element>()
719 .get_attribute(&ns!(), &local_name!("min"))
720 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
721 .or_else(|| self.default_minimum())
722 }
723
724 fn maximum(&self) -> Option<f64> {
726 self.upcast::<Element>()
727 .get_attribute(&ns!(), &local_name!("max"))
728 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
729 .or_else(|| self.default_maximum())
730 }
731
732 fn stepped_minimum(&self) -> Option<f64> {
735 match (self.minimum(), self.allowed_value_step()) {
736 (Some(min), Some(allowed_step)) => {
737 let step_base = self.step_base();
738 let nsteps = (min - step_base) / allowed_step;
740 Some(step_base + (allowed_step * nsteps.ceil()))
742 },
743 (_, _) => None,
744 }
745 }
746
747 fn stepped_maximum(&self) -> Option<f64> {
750 match (self.maximum(), self.allowed_value_step()) {
751 (Some(max), Some(allowed_step)) => {
752 let step_base = self.step_base();
753 let nsteps = (max - step_base) / allowed_step;
755 Some(step_base + (allowed_step * nsteps.floor()))
757 },
758 (_, _) => None,
759 }
760 }
761
762 fn default_minimum(&self) -> Option<f64> {
764 match self.input_type() {
765 InputType::Range => Some(0.0),
766 _ => None,
767 }
768 }
769
770 fn default_maximum(&self) -> Option<f64> {
772 match self.input_type() {
773 InputType::Range => Some(100.0),
774 _ => None,
775 }
776 }
777
778 fn default_range_value(&self) -> f64 {
780 let min = self.minimum().unwrap_or(0.0);
781 let max = self.maximum().unwrap_or(100.0);
782 if max < min {
783 min
784 } else {
785 min + (max - min) * 0.5
786 }
787 }
788
789 fn default_step(&self) -> Option<f64> {
791 match self.input_type() {
792 InputType::Date => Some(1.0),
793 InputType::Month => Some(1.0),
794 InputType::Week => Some(1.0),
795 InputType::Time => Some(60.0),
796 InputType::DatetimeLocal => Some(60.0),
797 InputType::Number => Some(1.0),
798 InputType::Range => Some(1.0),
799 _ => None,
800 }
801 }
802
803 fn step_scale_factor(&self) -> f64 {
805 match self.input_type() {
806 InputType::Date => 86400000.0,
807 InputType::Month => 1.0,
808 InputType::Week => 604800000.0,
809 InputType::Time => 1000.0,
810 InputType::DatetimeLocal => 1000.0,
811 InputType::Number => 1.0,
812 InputType::Range => 1.0,
813 _ => unreachable!(),
814 }
815 }
816
817 fn step_base(&self) -> f64 {
819 if let Some(minimum) = self
823 .upcast::<Element>()
824 .get_attribute(&ns!(), &local_name!("min"))
825 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
826 {
827 return minimum;
828 }
829
830 if let Some(value) = self
834 .upcast::<Element>()
835 .get_attribute(&ns!(), &local_name!("value"))
836 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
837 {
838 return value;
839 }
840
841 if let Some(default_step_base) = self.default_step_base() {
843 return default_step_base;
844 }
845
846 0.0
848 }
849
850 fn default_step_base(&self) -> Option<f64> {
852 match self.input_type() {
853 InputType::Week => Some(-259200000.0),
854 _ => None,
855 }
856 }
857
858 fn step_up_or_down(&self, n: i32, dir: StepDirection, can_gc: CanGc) -> ErrorResult {
862 if !self.does_value_as_number_apply() {
865 return Err(Error::InvalidState(None));
866 }
867 let step_base = self.step_base();
868
869 let Some(allowed_value_step) = self.allowed_value_step() else {
871 return Err(Error::InvalidState(None));
872 };
873
874 let minimum = self.minimum();
877 let maximum = self.maximum();
878 if let (Some(min), Some(max)) = (minimum, maximum) {
879 if min > max {
880 return Ok(());
881 }
882
883 if let Some(stepped_minimum) = self.stepped_minimum() {
887 if stepped_minimum > max {
888 return Ok(());
889 }
890 }
891 }
892
893 let mut value: f64 = self
897 .convert_string_to_number(&self.Value().str())
898 .unwrap_or(0.0);
899
900 let valueBeforeStepping = value;
902
903 if (value - step_base) % allowed_value_step != 0.0 {
908 value = match dir {
909 StepDirection::Down =>
910 {
912 let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
913 intervals_from_base * allowed_value_step + step_base
914 },
915 StepDirection::Up =>
916 {
918 let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
919 intervals_from_base * allowed_value_step + step_base
920 },
921 };
922 }
923 else {
925 value += match dir {
930 StepDirection::Down => -f64::from(n) * allowed_value_step,
931 StepDirection::Up => f64::from(n) * allowed_value_step,
932 };
933 }
934
935 if let Some(min) = minimum {
939 if value < min {
940 value = self.stepped_minimum().unwrap_or(value);
941 }
942 }
943
944 if let Some(max) = maximum {
948 if value > max {
949 value = self.stepped_maximum().unwrap_or(value);
950 }
951 }
952
953 match dir {
957 StepDirection::Down => {
958 if value > valueBeforeStepping {
959 return Ok(());
960 }
961 },
962 StepDirection::Up => {
963 if value < valueBeforeStepping {
964 return Ok(());
965 }
966 },
967 }
968
969 self.SetValueAsNumber(value, can_gc)
973 }
974
975 fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
977 let list_string = self
978 .upcast::<Element>()
979 .get_string_attribute(&local_name!("list"));
980 if list_string.is_empty() {
981 return None;
982 }
983 let ancestor = self
984 .upcast::<Node>()
985 .GetRootNode(&GetRootNodeOptions::empty());
986 let first_with_id = &ancestor
987 .traverse_preorder(ShadowIncluding::No)
988 .find(|node| {
989 node.downcast::<Element>()
990 .is_some_and(|e| e.Id() == list_string)
991 });
992 first_with_id
993 .as_ref()
994 .and_then(|el| el.downcast::<HTMLDataListElement>())
995 .map(DomRoot::from_ref)
996 }
997
998 fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
1000 match self.input_type() {
1001 InputType::Checkbox => self.Required() && !self.Checked(),
1003 InputType::Radio => {
1005 if self.radio_group_name().is_none() {
1006 return false;
1007 }
1008 let mut is_required = self.Required();
1009 let mut is_checked = self.Checked();
1010 let root = self
1011 .upcast::<Node>()
1012 .GetRootNode(&GetRootNodeOptions::empty());
1013 let form = self.form_owner();
1014 for other in radio_group_iter(
1015 self,
1016 self.radio_group_name().as_ref(),
1017 form.as_deref(),
1018 &root,
1019 ) {
1020 is_required = is_required || other.Required();
1021 is_checked = is_checked || other.Checked();
1022 }
1023 is_required && !is_checked
1024 },
1025 InputType::File => {
1027 self.Required() && self.filelist.get().is_none_or(|files| files.Length() == 0)
1028 },
1029 _ => {
1031 self.Required() &&
1032 self.value_mode() == ValueMode::Value &&
1033 self.is_mutable() &&
1034 value.is_empty()
1035 },
1036 }
1037 }
1038
1039 fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
1041 if value.is_empty() {
1042 return false;
1043 }
1044
1045 match self.input_type() {
1046 InputType::Url => Url::parse(&value.str()).is_err(),
1048 InputType::Email => {
1051 if self.Multiple() {
1052 !split_commas(&value.str()).all(|string| string.is_valid_email_address_string())
1053 } else {
1054 !value.str().is_valid_email_address_string()
1055 }
1056 },
1057 _ => false,
1059 }
1060 }
1061
1062 fn suffers_from_pattern_mismatch(&self, value: &DOMString, can_gc: CanGc) -> bool {
1064 let pattern_str = self.Pattern();
1067 if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
1068 return false;
1069 }
1070
1071 let cx = GlobalScope::get_cx();
1073 let _ac = enter_realm(self);
1074 rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>());
1075
1076 if compile_pattern(cx, &pattern_str.str(), pattern.handle_mut(), can_gc) {
1077 if self.Multiple() && self.does_multiple_apply() {
1078 !split_commas(&value.str())
1079 .all(|s| matches_js_regex(cx, pattern.handle(), s, can_gc).unwrap_or(true))
1080 } else {
1081 !matches_js_regex(cx, pattern.handle(), &value.str(), can_gc).unwrap_or(true)
1082 }
1083 } else {
1084 false
1086 }
1087 }
1088
1089 fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
1091 if value.is_empty() {
1092 return false;
1093 }
1094
1095 match self.input_type() {
1096 InputType::Email => {
1099 false
1103 },
1104 InputType::Date => !value.str().is_valid_date_string(),
1106 InputType::Month => !value.str().is_valid_month_string(),
1108 InputType::Week => !value.str().is_valid_week_string(),
1110 InputType::Time => !value.str().is_valid_time_string(),
1112 InputType::DatetimeLocal => !value.str().is_valid_local_date_time_string(),
1114 InputType::Number | InputType::Range => !value.is_valid_floating_point_number_string(),
1117 InputType::Color => !value.str().is_valid_simple_color_string(),
1119 _ => false,
1121 }
1122 }
1123
1124 fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
1127 let value_dirty = self.value_dirty.get();
1130 let textinput = self.textinput.borrow();
1131 let edit_by_user = !textinput.was_last_change_by_set_content();
1132
1133 if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
1134 return ValidationFlags::empty();
1135 }
1136
1137 let mut failed_flags = ValidationFlags::empty();
1138 let UTF16CodeUnits(value_len) = textinput.utf16_len();
1139 let min_length = self.MinLength();
1140 let max_length = self.MaxLength();
1141
1142 if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
1143 failed_flags.insert(ValidationFlags::TOO_SHORT);
1144 }
1145
1146 if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
1147 failed_flags.insert(ValidationFlags::TOO_LONG);
1148 }
1149
1150 failed_flags
1151 }
1152
1153 fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
1157 if value.is_empty() || !self.does_value_as_number_apply() {
1158 return ValidationFlags::empty();
1159 }
1160
1161 let Some(value_as_number) = self.convert_string_to_number(&value.str()) else {
1162 return ValidationFlags::empty();
1163 };
1164
1165 let mut failed_flags = ValidationFlags::empty();
1166 let min_value = self.minimum();
1167 let max_value = self.maximum();
1168
1169 let has_reversed_range = match (min_value, max_value) {
1171 (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
1172 _ => false,
1173 };
1174
1175 if has_reversed_range {
1176 if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
1178 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1179 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1180 }
1181 } else {
1182 if let Some(min_value) = min_value {
1184 if value_as_number < min_value {
1185 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1186 }
1187 }
1188 if let Some(max_value) = max_value {
1190 if value_as_number > max_value {
1191 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1192 }
1193 }
1194 }
1195
1196 if let Some(step) = self.allowed_value_step() {
1198 let diff = (self.step_base() - value_as_number) % step / value_as_number;
1202 if diff.abs() > 1e-12 {
1203 failed_flags.insert(ValidationFlags::STEP_MISMATCH);
1204 }
1205 }
1206
1207 failed_flags
1208 }
1209
1210 fn shadow_root(&self, can_gc: CanGc) -> DomRoot<ShadowRoot> {
1215 self.upcast::<Element>()
1216 .shadow_root()
1217 .unwrap_or_else(|| self.upcast::<Element>().attach_ua_shadow_root(true, can_gc))
1218 }
1219
1220 fn create_text_shadow_tree(&self, can_gc: CanGc) {
1221 let document = self.owner_document();
1222 let shadow_root = self.shadow_root(can_gc);
1223 Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
1224
1225 let inner_container = Element::create(
1226 QualName::new(None, ns!(html), local_name!("div")),
1227 None,
1228 &document,
1229 ElementCreator::ScriptCreated,
1230 CustomElementCreationMode::Asynchronous,
1231 None,
1232 can_gc,
1233 );
1234 shadow_root
1235 .upcast::<Node>()
1236 .AppendChild(inner_container.upcast::<Node>(), can_gc)
1237 .unwrap();
1238 inner_container
1239 .upcast::<Node>()
1240 .set_implemented_pseudo_element(PseudoElement::ServoTextControlInnerContainer);
1241
1242 let text_container = create_ua_widget_div_with_text_node(
1243 &document,
1244 inner_container.upcast::<Node>(),
1245 PseudoElement::ServoTextControlInnerEditor,
1246 false,
1247 can_gc,
1248 );
1249
1250 let _ = self
1251 .shadow_tree
1252 .borrow_mut()
1253 .insert(ShadowTree::Text(InputTypeTextShadowTree {
1254 inner_container: inner_container.as_traced(),
1255 text_container: text_container.as_traced(),
1256 placeholder_container: DomRefCell::new(None),
1257 }));
1258 }
1259
1260 fn text_shadow_tree(&self, can_gc: CanGc) -> Ref<'_, InputTypeTextShadowTree> {
1261 let has_text_shadow_tree = self
1262 .shadow_tree
1263 .borrow()
1264 .as_ref()
1265 .is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Text(_)));
1266 if !has_text_shadow_tree {
1267 self.create_text_shadow_tree(can_gc);
1268 }
1269
1270 let shadow_tree = self.shadow_tree.borrow();
1271 Ref::filter_map(shadow_tree, |shadow_tree| {
1272 let shadow_tree = shadow_tree.as_ref()?;
1273 match shadow_tree {
1274 ShadowTree::Text(text_tree) => Some(text_tree),
1275 _ => None,
1276 }
1277 })
1278 .ok()
1279 .expect("UA shadow tree was not created")
1280 }
1281
1282 fn create_color_shadow_tree(&self, can_gc: CanGc) {
1283 let document = self.owner_document();
1284 let shadow_root = self.shadow_root(can_gc);
1285 Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
1286
1287 let color_value = Element::create(
1288 QualName::new(None, ns!(html), local_name!("div")),
1289 None,
1290 &document,
1291 ElementCreator::ScriptCreated,
1292 CustomElementCreationMode::Asynchronous,
1293 None,
1294 can_gc,
1295 );
1296 shadow_root
1297 .upcast::<Node>()
1298 .AppendChild(color_value.upcast::<Node>(), can_gc)
1299 .unwrap();
1300 color_value
1301 .upcast::<Node>()
1302 .set_implemented_pseudo_element(PseudoElement::ColorSwatch);
1303
1304 let _ = self
1305 .shadow_tree
1306 .borrow_mut()
1307 .insert(ShadowTree::Color(InputTypeColorShadowTree {
1308 color_value: color_value.as_traced(),
1309 }));
1310 }
1311
1312 fn color_shadow_tree(&self, can_gc: CanGc) -> Ref<'_, InputTypeColorShadowTree> {
1319 let has_color_shadow_tree = self
1320 .shadow_tree
1321 .borrow()
1322 .as_ref()
1323 .is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Color(_)));
1324 if !has_color_shadow_tree {
1325 self.create_color_shadow_tree(can_gc);
1326 }
1327
1328 let shadow_tree = self.shadow_tree.borrow();
1329 Ref::filter_map(shadow_tree, |shadow_tree| {
1330 let shadow_tree = shadow_tree.as_ref()?;
1331 match shadow_tree {
1332 ShadowTree::Color(color_tree) => Some(color_tree),
1333 _ => None,
1334 }
1335 })
1336 .ok()
1337 .expect("UA shadow tree was not created")
1338 }
1339
1340 pub(crate) fn is_textual_widget(&self) -> bool {
1345 matches!(
1346 self.input_type(),
1347 InputType::Date |
1348 InputType::DatetimeLocal |
1349 InputType::Email |
1350 InputType::Month |
1351 InputType::Number |
1352 InputType::Password |
1353 InputType::Range |
1354 InputType::Search |
1355 InputType::Tel |
1356 InputType::Text |
1357 InputType::Time |
1358 InputType::Url |
1359 InputType::Week
1360 )
1361 }
1362
1363 fn update_textual_shadow_tree(&self, can_gc: CanGc) {
1367 debug_assert!(self.is_textual_widget());
1369
1370 let text_shadow_tree = self.text_shadow_tree(can_gc);
1371 let value = self.Value();
1372
1373 let value_text = match (value.is_empty(), self.input_type()) {
1382 (false, InputType::Password) => value
1384 .str()
1385 .chars()
1386 .map(|_| PASSWORD_REPLACEMENT_CHAR)
1387 .collect::<String>()
1388 .into(),
1389 (false, _) => value,
1390 (true, _) => "\u{200B}".into(),
1391 };
1392
1393 text_shadow_tree
1395 .text_container
1396 .upcast::<Node>()
1397 .GetFirstChild()
1398 .expect("UA widget text container without child")
1399 .downcast::<CharacterData>()
1400 .expect("First child is not a CharacterData node")
1401 .SetData(value_text);
1402 }
1403
1404 fn update_color_shadow_tree(&self, can_gc: CanGc) {
1405 debug_assert_eq!(self.input_type(), InputType::Color);
1407
1408 let color_shadow_tree = self.color_shadow_tree(can_gc);
1409 let mut value = self.Value();
1410 if value.str().is_valid_simple_color_string() {
1411 value.make_ascii_lowercase();
1412 } else {
1413 value = DOMString::from("#000000");
1414 }
1415 let style = format!("background-color: {value}");
1416 color_shadow_tree.color_value.set_string_attribute(
1417 &local_name!("style"),
1418 style.into(),
1419 can_gc,
1420 );
1421 }
1422
1423 fn update_shadow_tree(&self, can_gc: CanGc) {
1424 match self.input_type() {
1425 _ if self.is_textual_widget() => self.update_textual_shadow_tree(can_gc),
1426 InputType::Color => self.update_color_shadow_tree(can_gc),
1427 _ => {},
1428 }
1429 }
1430
1431 fn may_have_embedder_control(&self) -> bool {
1432 let el = self.upcast::<Element>();
1433 self.input_type() == InputType::Color && !el.disabled_state()
1434 }
1435
1436 fn handle_key_reaction(&self, action: KeyReaction, event: &Event, can_gc: CanGc) {
1437 match action {
1438 KeyReaction::TriggerDefaultAction => {
1439 self.implicit_submission(can_gc);
1440 },
1441 KeyReaction::DispatchInput(text, is_composing, input_type) => {
1442 if event.IsTrusted() {
1443 self.textinput.borrow().queue_input_event(
1444 self.upcast(),
1445 text,
1446 is_composing,
1447 input_type,
1448 );
1449 }
1450 self.value_dirty.set(true);
1451 self.update_placeholder_shown_state();
1452 self.upcast::<Node>().dirty(NodeDamage::Other);
1453 event.mark_as_handled();
1454 },
1455 KeyReaction::RedrawSelection => {
1456 self.upcast::<Node>().dirty(NodeDamage::Other);
1457 event.mark_as_handled();
1458 },
1459 KeyReaction::Nothing => (),
1460 }
1461 }
1462}
1463
1464pub(crate) trait LayoutHTMLInputElementHelpers<'dom> {
1465 fn value_for_layout(self) -> Cow<'dom, str>;
1467 fn size_for_layout(self) -> u32;
1468 fn selection_for_layout(self) -> Option<Range<usize>>;
1469}
1470
1471#[expect(unsafe_code)]
1472impl<'dom> LayoutDom<'dom, HTMLInputElement> {
1473 fn get_raw_textinput_value(self) -> DOMString {
1474 unsafe {
1475 self.unsafe_get()
1476 .textinput
1477 .borrow_for_layout()
1478 .get_content()
1479 }
1480 }
1481 fn get_filelist(self) -> Option<LayoutDom<'dom, FileList>> {
1482 unsafe { self.unsafe_get().filelist.get_inner_as_layout() }
1483 }
1484
1485 fn input_type(self) -> InputType {
1486 self.unsafe_get().input_type.get()
1487 }
1488
1489 fn textinput_sorted_selection_offsets_range(self) -> Range<UTF8Bytes> {
1490 unsafe {
1491 self.unsafe_get()
1492 .textinput
1493 .borrow_for_layout()
1494 .sorted_selection_offsets_range()
1495 }
1496 }
1497}
1498
1499impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElement> {
1500 fn value_for_layout(self) -> Cow<'dom, str> {
1504 fn get_raw_attr_value<'dom>(
1505 input: LayoutDom<'dom, HTMLInputElement>,
1506 default: &'static str,
1507 ) -> Cow<'dom, str> {
1508 input
1509 .upcast::<Element>()
1510 .get_attr_val_for_layout(&ns!(), &local_name!("value"))
1511 .unwrap_or(default)
1512 .into()
1513 }
1514
1515 match self.input_type() {
1516 InputType::Checkbox | InputType::Radio | InputType::Image | InputType::Hidden => {
1517 "".into()
1518 },
1519 InputType::File => {
1520 let filelist = self.get_filelist();
1521 match filelist {
1522 Some(filelist) => {
1523 let length = filelist.len();
1524 if length == 0 {
1525 DEFAULT_FILE_INPUT_VALUE.into()
1526 } else if length == 1 {
1527 match filelist.file_for_layout(0) {
1528 Some(file) => file.name().to_string().into(),
1529 None => DEFAULT_FILE_INPUT_VALUE.into(),
1530 }
1531 } else {
1532 format!("{} files", length).into()
1533 }
1534 },
1535 None => DEFAULT_FILE_INPUT_VALUE.into(),
1536 }
1537 },
1538 InputType::Button => get_raw_attr_value(self, ""),
1539 InputType::Submit => get_raw_attr_value(self, DEFAULT_SUBMIT_VALUE),
1540 InputType::Reset => get_raw_attr_value(self, DEFAULT_RESET_VALUE),
1541 InputType::Range => "".into(),
1543 _ => {
1544 unreachable!("Input with shadow tree should use internal shadow tree for layout");
1545 },
1546 }
1547 }
1548
1549 fn size_for_layout(self) -> u32 {
1559 self.unsafe_get().size.get()
1560 }
1561
1562 fn selection_for_layout(self) -> Option<Range<usize>> {
1563 if !self.upcast::<Element>().focus_state() {
1564 return None;
1565 }
1566
1567 let sorted_selection_offsets_range = self.textinput_sorted_selection_offsets_range();
1568
1569 match self.input_type() {
1570 InputType::Password => {
1571 let text = self.get_raw_textinput_value();
1572 let sel = UTF8Bytes::unwrap_range(sorted_selection_offsets_range);
1573
1574 let char_start = text.str()[..sel.start].chars().count();
1576 let char_end = char_start + text.str()[sel].chars().count();
1577
1578 let bytes_per_char = PASSWORD_REPLACEMENT_CHAR.len_utf8();
1579 Some(char_start * bytes_per_char..char_end * bytes_per_char)
1580 },
1581 input_type if input_type.is_textual() => {
1582 Some(UTF8Bytes::unwrap_range(sorted_selection_offsets_range))
1583 },
1584 _ => None,
1585 }
1586 }
1587}
1588
1589impl TextControlElement for HTMLInputElement {
1590 fn selection_api_applies(&self) -> bool {
1592 matches!(
1593 self.input_type(),
1594 InputType::Text |
1595 InputType::Search |
1596 InputType::Url |
1597 InputType::Tel |
1598 InputType::Password
1599 )
1600 }
1601
1602 fn has_selectable_text(&self) -> bool {
1610 match self.input_type() {
1611 InputType::Text |
1612 InputType::Search |
1613 InputType::Url |
1614 InputType::Tel |
1615 InputType::Password |
1616 InputType::Email |
1617 InputType::Date |
1618 InputType::Month |
1619 InputType::Week |
1620 InputType::Time |
1621 InputType::DatetimeLocal |
1622 InputType::Number => !self.textinput.borrow().get_content().is_empty(),
1623
1624 InputType::Button |
1625 InputType::Checkbox |
1626 InputType::Color |
1627 InputType::File |
1628 InputType::Hidden |
1629 InputType::Image |
1630 InputType::Radio |
1631 InputType::Range |
1632 InputType::Reset |
1633 InputType::Submit => false,
1634 }
1635 }
1636
1637 fn has_selection(&self) -> bool {
1638 self.textinput.borrow().has_selection()
1639 }
1640
1641 fn set_dirty_value_flag(&self, value: bool) {
1642 self.value_dirty.set(value)
1643 }
1644
1645 fn select_all(&self) {
1646 self.textinput.borrow_mut().select_all();
1647 self.upcast::<Node>().dirty(NodeDamage::Other);
1648 }
1649}
1650
1651#[allow(non_snake_case)]
1652impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1653 make_getter!(Accept, "accept");
1655
1656 make_setter!(SetAccept, "accept");
1658
1659 make_getter!(Alt, "alt");
1661
1662 make_setter!(SetAlt, "alt");
1664
1665 make_getter!(DirName, "dirname");
1667
1668 make_setter!(SetDirName, "dirname");
1670
1671 make_bool_getter!(Disabled, "disabled");
1673
1674 make_bool_setter!(SetDisabled, "disabled");
1676
1677 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1679 self.form_owner()
1680 }
1681
1682 fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1684 self.filelist.get().as_ref().cloned()
1685 }
1686
1687 fn SetFiles(&self, files: Option<&FileList>) {
1689 if self.input_type() == InputType::File && files.is_some() {
1690 self.filelist.set(files);
1691 }
1692 }
1693
1694 make_bool_getter!(DefaultChecked, "checked");
1696
1697 make_bool_setter!(SetDefaultChecked, "checked");
1699
1700 fn Checked(&self) -> bool {
1702 self.upcast::<Element>()
1703 .state()
1704 .contains(ElementState::CHECKED)
1705 }
1706
1707 fn SetChecked(&self, checked: bool, can_gc: CanGc) {
1709 self.update_checked_state(checked, true, can_gc);
1710 self.value_changed(can_gc);
1711 }
1712
1713 make_bool_getter!(ReadOnly, "readonly");
1715
1716 make_bool_setter!(SetReadOnly, "readonly");
1718
1719 make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1721
1722 make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1724
1725 fn Type(&self) -> DOMString {
1727 DOMString::from(self.input_type().as_str())
1728 }
1729
1730 make_atomic_setter!(SetType, "type");
1732
1733 fn Value(&self) -> DOMString {
1735 match self.value_mode() {
1736 ValueMode::Value => self.textinput.borrow().get_content(),
1737 ValueMode::Default => self
1738 .upcast::<Element>()
1739 .get_attribute(&ns!(), &local_name!("value"))
1740 .map_or(DOMString::from(""), |a| {
1741 DOMString::from(a.summarize().value)
1742 }),
1743 ValueMode::DefaultOn => self
1744 .upcast::<Element>()
1745 .get_attribute(&ns!(), &local_name!("value"))
1746 .map_or(DOMString::from("on"), |a| {
1747 DOMString::from(a.summarize().value)
1748 }),
1749 ValueMode::Filename => {
1750 let mut path = DOMString::from("");
1751 match self.filelist.get() {
1752 Some(ref fl) => match fl.Item(0) {
1753 Some(ref f) => {
1754 path.push_str("C:\\fakepath\\");
1755 path.push_str(&f.name().str());
1756 path
1757 },
1758 None => path,
1759 },
1760 None => path,
1761 }
1762 },
1763 }
1764 }
1765
1766 fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult {
1768 match self.value_mode() {
1769 ValueMode::Value => {
1770 {
1771 self.value_dirty.set(true);
1773
1774 self.sanitize_value(&mut value);
1776
1777 let mut textinput = self.textinput.borrow_mut();
1778
1779 if *textinput.single_line_content() != value {
1781 textinput.set_content(value);
1783
1784 textinput.clear_selection_to_limit(Direction::Forward);
1786 }
1787 }
1788
1789 self.update_placeholder_shown_state();
1793 },
1794 ValueMode::Default | ValueMode::DefaultOn => {
1795 self.upcast::<Element>()
1796 .set_string_attribute(&local_name!("value"), value, can_gc);
1797 },
1798 ValueMode::Filename => {
1799 if value.is_empty() {
1800 let window = self.owner_window();
1801 let fl = FileList::new(&window, vec![], can_gc);
1802 self.filelist.set(Some(&fl));
1803 } else {
1804 return Err(Error::InvalidState(None));
1805 }
1806 },
1807 }
1808
1809 self.value_changed(can_gc);
1810 self.upcast::<Node>().dirty(NodeDamage::Other);
1811 Ok(())
1812 }
1813
1814 make_getter!(DefaultValue, "value");
1816
1817 make_setter!(SetDefaultValue, "value");
1819
1820 make_getter!(Min, "min");
1822
1823 make_setter!(SetMin, "min");
1825
1826 fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1828 self.suggestions_source_element()
1829 }
1830
1831 #[expect(unsafe_code)]
1833 fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> {
1834 self.convert_string_to_naive_datetime(self.Value())
1835 .map(|date_time| unsafe {
1836 let time = ClippedTime {
1837 t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1838 };
1839 NonNull::new_unchecked(NewDateObject(*cx, time))
1840 })
1841 }
1842
1843 #[allow(non_snake_case)]
1845 #[expect(unsafe_code)]
1846 fn SetValueAsDate(
1847 &self,
1848 cx: SafeJSContext,
1849 value: *mut JSObject,
1850 can_gc: CanGc,
1851 ) -> ErrorResult {
1852 rooted!(in(*cx) let value = value);
1853 if !self.does_value_as_date_apply() {
1854 return Err(Error::InvalidState(None));
1855 }
1856 if value.is_null() {
1857 return self.SetValue(DOMString::from(""), can_gc);
1858 }
1859 let mut msecs: f64 = 0.0;
1860 unsafe {
1864 let mut isDate = false;
1865 if !ObjectIsDate(*cx, Handle::from(value.handle()), &mut isDate) {
1866 return Err(Error::JSFailed);
1867 }
1868 if !isDate {
1869 return Err(Error::Type("Value was not a date".to_string()));
1870 }
1871 if !DateGetMsecSinceEpoch(*cx, Handle::from(value.handle()), &mut msecs) {
1872 return Err(Error::JSFailed);
1873 }
1874 if !msecs.is_finite() {
1875 return self.SetValue(DOMString::from(""), can_gc);
1876 }
1877 }
1878
1879 let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1880 return self.SetValue(DOMString::from(""), can_gc);
1881 };
1882 self.SetValue(self.convert_datetime_to_dom_string(date_time), can_gc)
1883 }
1884
1885 fn ValueAsNumber(&self) -> f64 {
1887 self.convert_string_to_number(&self.Value().str())
1888 .unwrap_or(f64::NAN)
1889 }
1890
1891 fn SetValueAsNumber(&self, value: f64, can_gc: CanGc) -> ErrorResult {
1893 if value.is_infinite() {
1894 Err(Error::Type("value is not finite".to_string()))
1895 } else if !self.does_value_as_number_apply() {
1896 Err(Error::InvalidState(None))
1897 } else if value.is_nan() {
1898 self.SetValue(DOMString::from(""), can_gc)
1899 } else if let Some(converted) = self.convert_number_to_string(value) {
1900 self.SetValue(converted, can_gc)
1901 } else {
1902 self.SetValue(DOMString::from(""), can_gc)
1907 }
1908 }
1909
1910 make_getter!(Name, "name");
1912
1913 make_atomic_setter!(SetName, "name");
1915
1916 make_getter!(Placeholder, "placeholder");
1918
1919 make_setter!(SetPlaceholder, "placeholder");
1921
1922 make_form_action_getter!(FormAction, "formaction");
1924
1925 make_setter!(SetFormAction, "formaction");
1927
1928 make_enumerated_getter!(
1930 FormEnctype,
1931 "formenctype",
1932 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1933 invalid => "application/x-www-form-urlencoded"
1934 );
1935
1936 make_setter!(SetFormEnctype, "formenctype");
1938
1939 make_enumerated_getter!(
1941 FormMethod,
1942 "formmethod",
1943 "get" | "post" | "dialog",
1944 invalid => "get"
1945 );
1946
1947 make_setter!(SetFormMethod, "formmethod");
1949
1950 make_getter!(FormTarget, "formtarget");
1952
1953 make_setter!(SetFormTarget, "formtarget");
1955
1956 make_bool_getter!(FormNoValidate, "formnovalidate");
1958
1959 make_bool_setter!(SetFormNoValidate, "formnovalidate");
1961
1962 make_getter!(Max, "max");
1964
1965 make_setter!(SetMax, "max");
1967
1968 make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1970
1971 make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1973
1974 make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1976
1977 make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1979
1980 make_bool_getter!(Multiple, "multiple");
1982
1983 make_bool_setter!(SetMultiple, "multiple");
1985
1986 make_getter!(Pattern, "pattern");
1988
1989 make_setter!(SetPattern, "pattern");
1991
1992 make_bool_getter!(Required, "required");
1994
1995 make_bool_setter!(SetRequired, "required");
1997
1998 make_url_getter!(Src, "src");
2000
2001 make_url_setter!(SetSrc, "src");
2003
2004 make_getter!(Step, "step");
2006
2007 make_setter!(SetStep, "step");
2009
2010 fn Indeterminate(&self) -> bool {
2012 self.upcast::<Element>()
2013 .state()
2014 .contains(ElementState::INDETERMINATE)
2015 }
2016
2017 fn SetIndeterminate(&self, val: bool) {
2019 self.upcast::<Element>()
2020 .set_state(ElementState::INDETERMINATE, val)
2021 }
2022
2023 fn GetLabels(&self, can_gc: CanGc) -> Option<DomRoot<NodeList>> {
2027 if self.input_type() == InputType::Hidden {
2028 None
2029 } else {
2030 Some(self.labels_node_list.or_init(|| {
2031 NodeList::new_labels_list(
2032 self.upcast::<Node>().owner_doc().window(),
2033 self.upcast::<HTMLElement>(),
2034 can_gc,
2035 )
2036 }))
2037 }
2038 }
2039
2040 fn Select(&self) {
2042 self.selection().dom_select();
2043 }
2044
2045 fn GetSelectionStart(&self) -> Option<u32> {
2047 self.selection().dom_start()
2048 }
2049
2050 fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
2052 self.selection().set_dom_start(start)
2053 }
2054
2055 fn GetSelectionEnd(&self) -> Option<u32> {
2057 self.selection().dom_end()
2058 }
2059
2060 fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
2062 self.selection().set_dom_end(end)
2063 }
2064
2065 fn GetSelectionDirection(&self) -> Option<DOMString> {
2067 self.selection().dom_direction()
2068 }
2069
2070 fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
2072 self.selection().set_dom_direction(direction)
2073 }
2074
2075 fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
2077 self.selection().set_dom_range(start, end, direction)
2078 }
2079
2080 fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
2082 self.selection()
2083 .set_dom_range_text(replacement, None, None, Default::default())
2084 }
2085
2086 fn SetRangeText_(
2088 &self,
2089 replacement: DOMString,
2090 start: u32,
2091 end: u32,
2092 selection_mode: SelectionMode,
2093 ) -> ErrorResult {
2094 self.selection()
2095 .set_dom_range_text(replacement, Some(start), Some(end), selection_mode)
2096 }
2097
2098 fn SelectFiles(&self, paths: Vec<DOMString>) {
2101 if self.input_type() == InputType::File {
2102 self.select_files(Some(paths));
2103 }
2104 }
2105
2106 fn StepUp(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2108 self.step_up_or_down(n, StepDirection::Up, can_gc)
2109 }
2110
2111 fn StepDown(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2113 self.step_up_or_down(n, StepDirection::Down, can_gc)
2114 }
2115
2116 fn WillValidate(&self) -> bool {
2118 self.is_instance_validatable()
2119 }
2120
2121 fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
2123 self.validity_state(can_gc)
2124 }
2125
2126 fn CheckValidity(&self, can_gc: CanGc) -> bool {
2128 self.check_validity(can_gc)
2129 }
2130
2131 fn ReportValidity(&self, can_gc: CanGc) -> bool {
2133 self.report_validity(can_gc)
2134 }
2135
2136 fn ValidationMessage(&self) -> DOMString {
2138 self.validation_message()
2139 }
2140
2141 fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
2143 self.validity_state(can_gc).set_custom_error_message(error);
2144 }
2145}
2146
2147fn radio_group_iter<'a>(
2148 elem: &'a HTMLInputElement,
2149 group: Option<&'a Atom>,
2150 form: Option<&'a HTMLFormElement>,
2151 root: &'a Node,
2152) -> impl Iterator<Item = DomRoot<HTMLInputElement>> + 'a {
2153 root.traverse_preorder(ShadowIncluding::No)
2154 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2155 .filter(move |r| &**r == elem || in_same_group(r, form, group, Some(root)))
2156}
2157
2158fn broadcast_radio_checked(broadcaster: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2159 let root = broadcaster
2160 .upcast::<Node>()
2161 .GetRootNode(&GetRootNodeOptions::empty());
2162 let form = broadcaster.form_owner();
2163 for r in radio_group_iter(broadcaster, group, form.as_deref(), &root) {
2164 if broadcaster != &*r && r.Checked() {
2165 r.SetChecked(false, can_gc);
2166 }
2167 }
2168}
2169
2170fn perform_radio_group_validation(elem: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2171 let root = elem
2172 .upcast::<Node>()
2173 .GetRootNode(&GetRootNodeOptions::empty());
2174 let form = elem.form_owner();
2175 for r in radio_group_iter(elem, group, form.as_deref(), &root) {
2176 r.validity_state(can_gc)
2177 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2178 }
2179}
2180
2181fn in_same_group(
2183 other: &HTMLInputElement,
2184 owner: Option<&HTMLFormElement>,
2185 group: Option<&Atom>,
2186 tree_root: Option<&Node>,
2187) -> bool {
2188 if group.is_none() {
2189 return false;
2191 }
2192
2193 if other.input_type() != InputType::Radio ||
2194 other.form_owner().as_deref() != owner ||
2195 other.radio_group_name().as_ref() != group
2196 {
2197 return false;
2198 }
2199
2200 match tree_root {
2201 Some(tree_root) => {
2202 let other_root = other
2203 .upcast::<Node>()
2204 .GetRootNode(&GetRootNodeOptions::empty());
2205 tree_root == &*other_root
2206 },
2207 None => {
2208 true
2210 },
2211 }
2212}
2213
2214impl HTMLInputElement {
2215 fn radio_group_updated(&self, group: Option<&Atom>, can_gc: CanGc) {
2216 if self.Checked() {
2217 broadcast_radio_checked(self, group, can_gc);
2218 }
2219 }
2220
2221 pub(crate) fn form_datums(
2224 &self,
2225 submitter: Option<FormSubmitterElement>,
2226 encoding: Option<&'static Encoding>,
2227 ) -> Vec<FormDatum> {
2228 let ty = self.Type();
2232
2233 let name = self.Name();
2235 let is_submitter = match submitter {
2236 Some(FormSubmitterElement::Input(s)) => self == s,
2237 _ => false,
2238 };
2239
2240 match self.input_type() {
2241 InputType::Submit | InputType::Button | InputType::Reset if !is_submitter => {
2243 return vec![];
2244 },
2245
2246 InputType::Radio | InputType::Checkbox => {
2248 if !self.Checked() || name.is_empty() {
2249 return vec![];
2250 }
2251 },
2252
2253 InputType::File => {
2254 let mut datums = vec![];
2255
2256 let name = self.Name();
2258
2259 match self.GetFiles() {
2260 Some(fl) => {
2261 for f in fl.iter_files() {
2262 datums.push(FormDatum {
2263 ty: ty.clone(),
2264 name: name.clone(),
2265 value: FormDatumValue::File(DomRoot::from_ref(f)),
2266 });
2267 }
2268 },
2269 None => {
2270 datums.push(FormDatum {
2271 ty: ty.clone(),
2274 name: name.clone(),
2275 value: FormDatumValue::String(DOMString::from("")),
2276 })
2277 },
2278 }
2279
2280 return datums;
2281 },
2282
2283 InputType::Image => return vec![], InputType::Hidden => {
2287 if name.to_ascii_lowercase() == "_charset_" {
2288 return vec![FormDatum {
2289 ty: ty.clone(),
2290 name,
2291 value: FormDatumValue::String(match encoding {
2292 None => DOMString::from("UTF-8"),
2293 Some(enc) => DOMString::from(enc.name()),
2294 }),
2295 }];
2296 }
2297 },
2298
2299 _ => {
2301 if name.is_empty() {
2302 return vec![];
2303 }
2304 },
2305 }
2306
2307 vec![FormDatum {
2309 ty: ty.clone(),
2310 name,
2311 value: FormDatumValue::String(self.Value()),
2312 }]
2313 }
2314
2315 fn radio_group_name(&self) -> Option<Atom> {
2317 self.upcast::<Element>()
2318 .get_name()
2319 .filter(|name| !name.is_empty())
2320 }
2321
2322 fn update_checked_state(&self, checked: bool, dirty: bool, can_gc: CanGc) {
2323 self.upcast::<Element>()
2324 .set_state(ElementState::CHECKED, checked);
2325
2326 if dirty {
2327 self.checked_changed.set(true);
2328 }
2329
2330 if self.input_type() == InputType::Radio && checked {
2331 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
2332 }
2333
2334 self.upcast::<Node>().dirty(NodeDamage::Other);
2335 }
2336
2337 pub(crate) fn is_mutable(&self) -> bool {
2339 !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
2342 }
2343
2344 pub(crate) fn reset(&self, can_gc: CanGc) {
2346 match self.input_type() {
2347 InputType::Radio | InputType::Checkbox => {
2348 self.update_checked_state(self.DefaultChecked(), false, can_gc);
2349 self.checked_changed.set(false);
2350 self.value_changed(can_gc);
2351 },
2352 InputType::Image => (),
2353 _ => (),
2354 }
2355 self.textinput.borrow_mut().set_content(self.DefaultValue());
2356 self.value_dirty.set(false);
2357 self.upcast::<Node>().dirty(NodeDamage::Other);
2358 }
2359
2360 pub(crate) fn clear(&self, can_gc: CanGc) {
2363 self.value_dirty.set(false);
2365 self.checked_changed.set(false);
2366 self.textinput.borrow_mut().set_content(DOMString::from(""));
2368 self.update_checked_state(self.DefaultChecked(), false, can_gc);
2370 self.value_changed(can_gc);
2371 if self.filelist.get().is_some() {
2373 let window = self.owner_window();
2374 let filelist = FileList::new(&window, vec![], can_gc);
2375 self.filelist.set(Some(&filelist));
2376 }
2377 self.enable_sanitization();
2381 self.upcast::<Node>().dirty(NodeDamage::Other);
2382 }
2383
2384 fn update_placeholder_shown_state(&self) {
2385 if !self.input_type().is_textual_or_password() {
2386 return;
2387 }
2388
2389 let has_placeholder = !self.placeholder.borrow().is_empty();
2390 let has_value = !self.textinput.borrow().is_empty();
2391 let el = self.upcast::<Element>();
2392
2393 el.set_placeholder_shown_state(has_placeholder && !has_value);
2394 }
2395
2396 fn update_text_shadow_tree_placeholder(&self, can_gc: CanGc) {
2399 if !self.is_textual_widget() {
2400 return;
2401 }
2402
2403 let text_shadow_tree = self.text_shadow_tree(can_gc);
2404 text_shadow_tree.init_placeholder_container_if_necessary(self, can_gc);
2405
2406 let Some(ref placeholder_container) = *text_shadow_tree.placeholder_container.borrow()
2407 else {
2408 return;
2410 };
2411 let placeholder_text = self.placeholder.borrow().clone();
2412
2413 placeholder_container
2415 .upcast::<Node>()
2416 .GetFirstChild()
2417 .expect("UA widget text container without child")
2418 .downcast::<CharacterData>()
2419 .expect("First child is not a CharacterData node")
2420 .SetData(placeholder_text);
2421 }
2422
2423 pub(crate) fn select_files_for_webdriver(
2424 &self,
2425 test_paths: Vec<DOMString>,
2426 response_sender: IpcSender<Result<bool, ErrorStatus>>,
2427 ) {
2428 let mut stored_sender = self.pending_webdriver_response.borrow_mut();
2429 assert!(stored_sender.is_none());
2430
2431 *stored_sender = Some(PendingWebDriverResponse {
2432 response_sender,
2433 expected_file_count: test_paths.len(),
2434 });
2435
2436 self.select_files(Some(test_paths));
2437 }
2438
2439 pub(crate) fn select_files(&self, test_paths: Option<Vec<DOMString>>) {
2443 let current_paths = match &test_paths {
2444 Some(test_paths) => test_paths
2445 .iter()
2446 .filter_map(|path_str| PathBuf::from_str(&path_str.str()).ok())
2447 .collect(),
2448 None => Default::default(),
2451 };
2452
2453 let accept_current_paths_for_testing = test_paths.is_some();
2454 self.owner_document()
2455 .embedder_controls()
2456 .show_embedder_control(
2457 ControlElement::FileInput(DomRoot::from_ref(self)),
2458 EmbedderControlRequest::FilePicker(FilePickerRequest {
2459 origin: get_blob_origin(&self.owner_window().get_url()),
2460 current_paths,
2461 filter_patterns: filter_from_accept(&self.Accept()),
2462 allow_select_multiple: self.Multiple(),
2463 accept_current_paths_for_testing,
2464 }),
2465 None,
2466 );
2467 }
2468
2469 fn sanitize_value(&self, value: &mut DOMString) {
2471 if !self.sanitization_flag.get() {
2476 return;
2477 }
2478 match self.input_type() {
2479 InputType::Text | InputType::Search | InputType::Tel | InputType::Password => {
2480 value.strip_newlines();
2481 },
2482 InputType::Url => {
2483 value.strip_newlines();
2484 value.strip_leading_and_trailing_ascii_whitespace();
2485 },
2486 InputType::Date => {
2487 if !value.str().is_valid_date_string() {
2488 value.clear();
2489 }
2490 },
2491 InputType::Month => {
2492 if !value.str().is_valid_month_string() {
2493 value.clear();
2494 }
2495 },
2496 InputType::Week => {
2497 if !value.str().is_valid_week_string() {
2498 value.clear();
2499 }
2500 },
2501 InputType::Color => {
2502 if value.str().is_valid_simple_color_string() {
2503 value.make_ascii_lowercase();
2504 } else {
2505 *value = "#000000".into();
2506 }
2507 },
2508 InputType::Time => {
2509 if !value.str().is_valid_time_string() {
2510 value.clear();
2511 }
2512 },
2513 InputType::DatetimeLocal => {
2514 let time = value
2515 .str()
2516 .parse_local_date_time_string()
2517 .map(|date_time| date_time.to_local_date_time_string());
2518 match time {
2519 Some(normalized_string) => *value = DOMString::from_string(normalized_string),
2520 None => value.clear(),
2521 }
2522 },
2523 InputType::Number => {
2524 if !value.is_valid_floating_point_number_string() {
2525 value.clear();
2526 }
2527 },
2534 InputType::Range => {
2536 if !value.is_valid_floating_point_number_string() {
2537 *value = DOMString::from(self.default_range_value().to_string());
2538 }
2539 if let Ok(fval) = &value.parse::<f64>() {
2540 let mut fval = *fval;
2541 if let Some(max) = self.maximum() {
2544 if fval > max {
2545 fval = max;
2546 }
2547 }
2548 if let Some(min) = self.minimum() {
2549 if fval < min {
2550 fval = min;
2551 }
2552 }
2553 if let Some(allowed_value_step) = self.allowed_value_step() {
2558 let step_base = self.step_base();
2559 let steps_from_base = (fval - step_base) / allowed_value_step;
2560 if steps_from_base.fract() != 0.0 {
2561 let int_steps = round_halves_positive(steps_from_base);
2564 fval = int_steps * allowed_value_step + step_base;
2566
2567 if let Some(stepped_maximum) = self.stepped_maximum() {
2571 if fval > stepped_maximum {
2572 fval = stepped_maximum;
2573 }
2574 }
2575 if let Some(stepped_minimum) = self.stepped_minimum() {
2576 if fval < stepped_minimum {
2577 fval = stepped_minimum;
2578 }
2579 }
2580 }
2581 }
2582 *value = DOMString::from(fval.to_string());
2583 };
2584 },
2585 InputType::Email => {
2586 if !self.Multiple() {
2587 value.strip_newlines();
2588 value.strip_leading_and_trailing_ascii_whitespace();
2589 } else {
2590 let sanitized = split_commas(&value.str())
2591 .map(|token| {
2592 let mut token = DOMString::from(token.to_string());
2593 token.strip_newlines();
2594 token.strip_leading_and_trailing_ascii_whitespace();
2595 token
2596 })
2597 .join(",");
2598 value.clear();
2599 value.push_str(sanitized.as_str());
2600 }
2601 },
2602 InputType::Button |
2605 InputType::Checkbox |
2606 InputType::File |
2607 InputType::Hidden |
2608 InputType::Image |
2609 InputType::Radio |
2610 InputType::Reset |
2611 InputType::Submit => (),
2612 }
2613 }
2614
2615 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
2616 fn selection(&self) -> TextControlSelection<'_, Self> {
2617 TextControlSelection::new(self, &self.textinput)
2618 }
2619
2620 fn implicit_submission(&self, can_gc: CanGc) {
2622 let doc = self.owner_document();
2623 let node = doc.upcast::<Node>();
2624 let owner = self.form_owner();
2625 let form = match owner {
2626 None => return,
2627 Some(ref f) => f,
2628 };
2629
2630 if self.upcast::<Element>().click_in_progress() {
2631 return;
2632 }
2633 let submit_button = node
2634 .query_selector_iter(DOMString::from("input[type=submit]"))
2635 .unwrap()
2636 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2637 .find(|r| r.form_owner() == owner);
2638 match submit_button {
2639 Some(ref button) => {
2640 if button.is_instance_activatable() {
2641 button
2644 .upcast::<Node>()
2645 .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
2646 }
2647 },
2648 None => {
2649 let mut inputs = node
2650 .query_selector_iter(DOMString::from("input"))
2651 .unwrap()
2652 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2653 .filter(|input| {
2654 input.form_owner() == owner &&
2655 matches!(
2656 input.input_type(),
2657 InputType::Text |
2658 InputType::Search |
2659 InputType::Url |
2660 InputType::Tel |
2661 InputType::Email |
2662 InputType::Password |
2663 InputType::Date |
2664 InputType::Month |
2665 InputType::Week |
2666 InputType::Time |
2667 InputType::DatetimeLocal |
2668 InputType::Number
2669 )
2670 });
2671
2672 if inputs.nth(1).is_some() {
2673 return;
2675 }
2676 form.submit(
2677 SubmittedFrom::NotFromForm,
2678 FormSubmitterElement::Form(form),
2679 can_gc,
2680 );
2681 },
2682 }
2683 }
2684
2685 fn convert_string_to_number(&self, value: &str) -> Option<f64> {
2687 match self.input_type() {
2688 InputType::Date => value.parse_date_string().map(|date_time| {
2695 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2696 }),
2697 InputType::Month => value.parse_month_string().map(|date_time| {
2706 ((date_time.year() - 1970) * 12) as f64 + (date_time.month() as u8 - 1) as f64
2707 }),
2708 InputType::Week => value.parse_week_string().map(|date_time| {
2715 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2716 }),
2717 InputType::Time => value
2722 .parse_time_string()
2723 .map(|date_time| (date_time.time() - Time::MIDNIGHT).whole_milliseconds() as f64),
2724 InputType::DatetimeLocal => value.parse_local_date_time_string().map(|date_time| {
2731 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2732 }),
2733 InputType::Number | InputType::Range => parse_floating_point_number(value),
2734 _ => None,
2737 }
2738 }
2739
2740 fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
2742 match self.input_type() {
2743 InputType::Date | InputType::Week | InputType::Time | InputType::DatetimeLocal => {
2744 OffsetDateTime::from_unix_timestamp_nanos((value * 1e6) as i128)
2745 .ok()
2746 .map(|value| self.convert_datetime_to_dom_string(value))
2747 },
2748 InputType::Month => {
2749 let date = OffsetDateTime::UNIX_EPOCH;
2753 let years = (value / 12.) as i32;
2754 let year = date.year() + years;
2755
2756 let months = value as i32 - (years * 12);
2757 let months = match months.cmp(&0) {
2758 Ordering::Less => (12 - months) as u8,
2759 Ordering::Equal | Ordering::Greater => months as u8,
2760 } + 1;
2761
2762 let date = date
2763 .replace_year(year)
2764 .ok()?
2765 .replace_month(Month::try_from(months).ok()?)
2766 .ok()?;
2767 Some(self.convert_datetime_to_dom_string(date))
2768 },
2769 InputType::Number | InputType::Range => {
2770 let mut value = DOMString::from(value.to_string());
2771 value.set_best_representation_of_the_floating_point_number();
2772 Some(value)
2773 },
2774 _ => unreachable!("Should not have called convert_number_to_string for non-Date types"),
2775 }
2776 }
2777
2778 fn convert_string_to_naive_datetime(&self, value: DOMString) -> Option<OffsetDateTime> {
2782 match self.input_type() {
2783 InputType::Date => value.str().parse_date_string(),
2784 InputType::Time => value.str().parse_time_string(),
2785 InputType::Week => value.str().parse_week_string(),
2786 InputType::Month => value.str().parse_month_string(),
2787 InputType::DatetimeLocal => value.str().parse_local_date_time_string(),
2788 _ => None,
2790 }
2791 }
2792
2793 fn convert_datetime_to_dom_string(&self, value: OffsetDateTime) -> DOMString {
2797 DOMString::from_string(match self.input_type() {
2798 InputType::Date => value.to_date_string(),
2799 InputType::Month => value.to_month_string(),
2800 InputType::Week => value.to_week_string(),
2801 InputType::Time => value.to_time_string(),
2802 InputType::DatetimeLocal => value.to_local_date_time_string(),
2803 _ => {
2804 unreachable!("Should not have called convert_datetime_to_string for non-Date types")
2805 },
2806 })
2807 }
2808
2809 fn update_related_validity_states(&self, can_gc: CanGc) {
2810 match self.input_type() {
2811 InputType::Radio => {
2812 perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc)
2813 },
2814 _ => {
2815 self.validity_state(can_gc)
2816 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2817 },
2818 }
2819 }
2820
2821 fn value_changed(&self, can_gc: CanGc) {
2822 self.update_related_validity_states(can_gc);
2823 self.update_shadow_tree(can_gc);
2824 }
2825
2826 fn show_the_picker_if_applicable(&self) {
2828 if !self.is_mutable() {
2832 return;
2833 }
2834
2835 if self.input_type() == InputType::Color {
2838 let document = self.owner_document();
2839 let current_value = self.Value();
2840 let current_color = RgbColor {
2841 red: u8::from_str_radix(¤t_value.str()[1..3], 16).unwrap(),
2842 green: u8::from_str_radix(¤t_value.str()[3..5], 16).unwrap(),
2843 blue: u8::from_str_radix(¤t_value.str()[5..7], 16).unwrap(),
2844 };
2845 document.embedder_controls().show_embedder_control(
2846 ControlElement::ColorInput(DomRoot::from_ref(self)),
2847 EmbedderControlRequest::ColorPicker(current_color),
2848 None,
2849 );
2850 }
2851 }
2852
2853 pub(crate) fn handle_color_picker_response(&self, response: Option<RgbColor>, can_gc: CanGc) {
2854 let Some(selected_color) = response else {
2855 return;
2856 };
2857 let formatted_color = format!(
2858 "#{:0>2x}{:0>2x}{:0>2x}",
2859 selected_color.red, selected_color.green, selected_color.blue
2860 );
2861 let _ = self.SetValue(formatted_color.into(), can_gc);
2862 }
2863
2864 pub(crate) fn handle_file_picker_response(
2865 &self,
2866 response: Option<Vec<SelectedFile>>,
2867 can_gc: CanGc,
2868 ) {
2869 let mut files = Vec::new();
2870
2871 if let Some(pending_webdriver_reponse) = self.pending_webdriver_response.borrow_mut().take()
2872 {
2873 if self.Multiple() {
2880 if let Some(filelist) = self.filelist.get() {
2881 files = filelist.iter_files().map(|file| file.as_rooted()).collect();
2882 }
2883 }
2884
2885 let number_files_selected = response.as_ref().map(Vec::len).unwrap_or_default();
2886 pending_webdriver_reponse.finish(number_files_selected);
2887 }
2888
2889 let Some(response_files) = response else {
2890 return;
2891 };
2892
2893 let window = self.owner_window();
2894 files.extend(
2895 response_files
2896 .into_iter()
2897 .map(|file| File::new_from_selected(&window, file, can_gc)),
2898 );
2899
2900 if !self.Multiple() {
2903 files = files
2904 .pop()
2905 .map(|last_file| vec![last_file])
2906 .unwrap_or_default();
2907 }
2908
2909 self.filelist
2910 .set(Some(&FileList::new(&window, files, can_gc)));
2911
2912 let target = self.upcast::<EventTarget>();
2913 target.fire_event_with_params(
2914 atom!("input"),
2915 EventBubbles::Bubbles,
2916 EventCancelable::NotCancelable,
2917 EventComposed::Composed,
2918 can_gc,
2919 );
2920 target.fire_bubbling_event(atom!("change"), can_gc);
2921 }
2922
2923 fn handle_focus(&self) {
2924 let Ok(input_method_type) = self.input_type().try_into() else {
2925 return;
2926 };
2927
2928 self.owner_document()
2929 .embedder_controls()
2930 .show_embedder_control(
2931 ControlElement::Ime(DomRoot::from_ref(self.upcast())),
2932 EmbedderControlRequest::InputMethod(InputMethodRequest {
2933 input_method_type,
2934 text: self.Value().to_string(),
2935 insertion_point: self.GetSelectionEnd(),
2936 multiline: false,
2937 }),
2938 None,
2939 );
2940 }
2941}
2942
2943impl VirtualMethods for HTMLInputElement {
2944 fn super_type(&self) -> Option<&dyn VirtualMethods> {
2945 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
2946 }
2947
2948 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
2949 let could_have_had_embedder_control = self.may_have_embedder_control();
2950
2951 self.super_type()
2952 .unwrap()
2953 .attribute_mutated(attr, mutation, can_gc);
2954
2955 match *attr.local_name() {
2956 local_name!("disabled") => {
2957 let disabled_state = match mutation {
2958 AttributeMutation::Set(None, _) => true,
2959 AttributeMutation::Set(Some(_), _) => {
2960 return;
2962 },
2963 AttributeMutation::Removed => false,
2964 };
2965 let el = self.upcast::<Element>();
2966 el.set_disabled_state(disabled_state);
2967 el.set_enabled_state(!disabled_state);
2968 el.check_ancestors_disabled_state_for_form_control();
2969
2970 if self.input_type().is_textual() {
2971 let read_write = !(self.ReadOnly() || el.disabled_state());
2972 el.set_read_write_state(read_write);
2973 }
2974
2975 el.update_sequentially_focusable_status(can_gc);
2976 },
2977 local_name!("checked") if !self.checked_changed.get() => {
2978 let checked_state = match mutation {
2979 AttributeMutation::Set(None, _) => true,
2980 AttributeMutation::Set(Some(_), _) => {
2981 return;
2983 },
2984 AttributeMutation::Removed => false,
2985 };
2986 self.update_checked_state(checked_state, false, can_gc);
2987 },
2988 local_name!("size") => {
2989 let size = mutation.new_value(attr).map(|value| value.as_uint());
2990 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
2991 },
2992 local_name!("type") => {
2993 let el = self.upcast::<Element>();
2994 match mutation {
2995 AttributeMutation::Set(..) => {
2996 let new_type = InputType::from(attr.value().as_atom());
2997
2998 let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
3000 let previously_selectable = self.selection_api_applies();
3001
3002 self.input_type.set(new_type);
3003
3004 if new_type.is_textual() {
3005 let read_write = !(self.ReadOnly() || el.disabled_state());
3006 el.set_read_write_state(read_write);
3007 } else {
3008 el.set_read_write_state(false);
3009 }
3010
3011 if new_type == InputType::File {
3012 let window = self.owner_window();
3013 let filelist = FileList::new(&window, vec![], can_gc);
3014 self.filelist.set(Some(&filelist));
3015 }
3016
3017 let new_value_mode = self.value_mode();
3018 match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
3019 (&ValueMode::Value, false, ValueMode::Default) |
3021 (&ValueMode::Value, false, ValueMode::DefaultOn) => {
3022 self.SetValue(old_idl_value, can_gc)
3023 .expect("Failed to set input value on type change to a default ValueMode.");
3024 },
3025
3026 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
3028 self.SetValue(
3029 self.upcast::<Element>()
3030 .get_attribute(&ns!(), &local_name!("value"))
3031 .map_or(DOMString::from(""), |a| {
3032 DOMString::from(a.summarize().value)
3033 }),
3034 can_gc,
3035 )
3036 .expect(
3037 "Failed to set input value on type change to ValueMode::Value.",
3038 );
3039 self.value_dirty.set(false);
3040 },
3041
3042 (_, _, ValueMode::Filename)
3044 if old_value_mode != ValueMode::Filename =>
3045 {
3046 self.SetValue(DOMString::from(""), can_gc)
3047 .expect("Failed to set input value on type change to ValueMode::Filename.");
3048 },
3049 _ => {},
3050 }
3051
3052 if new_type == InputType::Radio {
3054 self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3055 }
3056
3057 let mut textinput = self.textinput.borrow_mut();
3059 let mut value = textinput.single_line_content().clone();
3060 self.sanitize_value(&mut value);
3061 textinput.set_content(value);
3062 self.upcast::<Node>().dirty(NodeDamage::Other);
3063
3064 if !previously_selectable && self.selection_api_applies() {
3066 textinput.clear_selection_to_limit(Direction::Backward);
3067 }
3068 },
3069 AttributeMutation::Removed => {
3070 if self.input_type() == InputType::Radio {
3071 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
3072 }
3073 self.input_type.set(InputType::default());
3074 let el = self.upcast::<Element>();
3075
3076 let read_write = !(self.ReadOnly() || el.disabled_state());
3077 el.set_read_write_state(read_write);
3078 },
3079 }
3080
3081 self.update_placeholder_shown_state();
3082 self.update_text_shadow_tree_placeholder(can_gc);
3083 },
3084 local_name!("value") if !self.value_dirty.get() => {
3087 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
3088 let mut value = value.map_or(DOMString::new(), DOMString::from);
3089
3090 self.sanitize_value(&mut value);
3091 self.textinput.borrow_mut().set_content(value);
3092 self.update_placeholder_shown_state();
3093
3094 self.upcast::<Node>().dirty(NodeDamage::Other);
3095 },
3096 local_name!("name") if self.input_type() == InputType::Radio => {
3097 self.radio_group_updated(
3098 mutation.new_value(attr).as_ref().map(|name| name.as_atom()),
3099 can_gc,
3100 );
3101 },
3102 local_name!("maxlength") => match *attr.value() {
3103 AttrValue::Int(_, value) => {
3104 let mut textinput = self.textinput.borrow_mut();
3105
3106 if value < 0 {
3107 textinput.set_max_length(None);
3108 } else {
3109 textinput.set_max_length(Some(UTF16CodeUnits(value as usize)))
3110 }
3111 },
3112 _ => panic!("Expected an AttrValue::Int"),
3113 },
3114 local_name!("minlength") => match *attr.value() {
3115 AttrValue::Int(_, value) => {
3116 let mut textinput = self.textinput.borrow_mut();
3117
3118 if value < 0 {
3119 textinput.set_min_length(None);
3120 } else {
3121 textinput.set_min_length(Some(UTF16CodeUnits(value as usize)))
3122 }
3123 },
3124 _ => panic!("Expected an AttrValue::Int"),
3125 },
3126 local_name!("placeholder") => {
3127 {
3128 let mut placeholder = self.placeholder.borrow_mut();
3129 placeholder.clear();
3130 if let AttributeMutation::Set(..) = mutation {
3131 placeholder
3132 .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
3133 }
3134 }
3135 self.update_placeholder_shown_state();
3136 self.update_text_shadow_tree_placeholder(can_gc);
3137 },
3138 local_name!("readonly") => {
3139 if self.input_type().is_textual() {
3140 let el = self.upcast::<Element>();
3141 match mutation {
3142 AttributeMutation::Set(..) => {
3143 el.set_read_write_state(false);
3144 },
3145 AttributeMutation::Removed => {
3146 el.set_read_write_state(!el.disabled_state());
3147 },
3148 }
3149 }
3150 },
3151 local_name!("form") => {
3152 self.form_attribute_mutated(mutation, can_gc);
3153 },
3154 _ => {},
3155 }
3156
3157 self.value_changed(can_gc);
3158
3159 if could_have_had_embedder_control && !self.may_have_embedder_control() {
3160 self.owner_document()
3161 .embedder_controls()
3162 .hide_embedder_control(self.upcast());
3163 }
3164 }
3165
3166 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
3167 match *name {
3168 local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
3169 local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
3170 local_name!("type") => AttrValue::from_atomic(value.into()),
3171 local_name!("maxlength") => {
3172 AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
3173 },
3174 local_name!("minlength") => {
3175 AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
3176 },
3177 _ => self
3178 .super_type()
3179 .unwrap()
3180 .parse_plain_attribute(name, value),
3181 }
3182 }
3183
3184 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
3185 if let Some(s) = self.super_type() {
3186 s.bind_to_tree(context, can_gc);
3187 }
3188 self.upcast::<Element>()
3189 .check_ancestors_disabled_state_for_form_control();
3190
3191 if self.input_type() == InputType::Radio {
3192 self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3193 }
3194
3195 self.value_changed(can_gc);
3196 }
3197
3198 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
3199 let form_owner = self.form_owner();
3200
3201 self.super_type().unwrap().unbind_from_tree(context, can_gc);
3202
3203 let node = self.upcast::<Node>();
3204 let el = self.upcast::<Element>();
3205 if node
3206 .ancestors()
3207 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
3208 {
3209 el.check_ancestors_disabled_state_for_form_control();
3210 } else {
3211 el.check_disabled_attribute();
3212 }
3213
3214 if self.input_type() == InputType::Radio {
3215 let root = context.parent.GetRootNode(&GetRootNodeOptions::empty());
3216 for r in radio_group_iter(
3217 self,
3218 self.radio_group_name().as_ref(),
3219 form_owner.as_deref(),
3220 &root,
3221 ) {
3222 r.validity_state(can_gc)
3223 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3224 }
3225 }
3226
3227 self.validity_state(can_gc)
3228 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3229
3230 if self.input_type() == InputType::Color {
3231 self.owner_document()
3232 .embedder_controls()
3233 .hide_embedder_control(self.upcast());
3234 }
3235 }
3236
3237 fn handle_event(&self, event: &Event, can_gc: CanGc) {
3243 if let Some(s) = self.super_type() {
3244 s.handle_event(event, can_gc);
3245 }
3246
3247 if event.type_() == atom!("click") && !event.DefaultPrevented() {
3248 if self.input_type().is_textual_or_password() &&
3254 !self.textinput.borrow().is_empty()
3256 {
3257 if let Some(mouse_event) = event.downcast::<MouseEvent>() {
3258 if let Some(point_in_target) = mouse_event.point_in_target() {
3262 let window = self.owner_window();
3263
3264 let edit_point_index = window
3266 .text_index_query(self.upcast::<Node>(), point_in_target.to_untyped())
3267 .unwrap_or_else(|| self.textinput.borrow().char_count());
3268 self.textinput.borrow_mut().clear_selection();
3269 self.textinput
3270 .borrow_mut()
3271 .set_edit_point_index(edit_point_index);
3272 self.upcast::<Node>().dirty(NodeDamage::Other);
3273 event.PreventDefault();
3274 }
3275 }
3276 }
3277 } else if event.type_() == atom!("keydown") &&
3278 !event.DefaultPrevented() &&
3279 self.input_type().is_textual_or_password()
3280 {
3281 if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
3282 let action = self.textinput.borrow_mut().handle_keydown(keyevent);
3285 self.handle_key_reaction(action, event, can_gc);
3286 }
3287 } else if event.type_() == atom!("keypress") &&
3288 !event.DefaultPrevented() &&
3289 self.input_type().is_textual_or_password()
3290 {
3291 } else if (event.type_() == atom!("compositionstart") ||
3295 event.type_() == atom!("compositionupdate") ||
3296 event.type_() == atom!("compositionend")) &&
3297 self.input_type().is_textual_or_password()
3298 {
3299 if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
3300 if event.type_() == atom!("compositionend") {
3301 let action = self
3302 .textinput
3303 .borrow_mut()
3304 .handle_compositionend(compositionevent);
3305 self.handle_key_reaction(action, event, can_gc);
3306 self.upcast::<Node>().dirty(NodeDamage::Other);
3307 self.update_placeholder_shown_state();
3308 } else if event.type_() == atom!("compositionupdate") {
3309 let action = self
3310 .textinput
3311 .borrow_mut()
3312 .handle_compositionupdate(compositionevent);
3313 self.handle_key_reaction(action, event, can_gc);
3314 self.upcast::<Node>().dirty(NodeDamage::Other);
3315 self.update_placeholder_shown_state();
3316 } else if event.type_() == atom!("compositionstart") {
3317 self.update_placeholder_shown_state();
3319 }
3320 event.mark_as_handled();
3321 }
3322 } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
3323 let reaction = self
3324 .textinput
3325 .borrow_mut()
3326 .handle_clipboard_event(clipboard_event);
3327 let flags = reaction.flags;
3328 if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
3329 self.owner_document().event_handler().fire_clipboard_event(
3330 None,
3331 ClipboardEventType::Change,
3332 can_gc,
3333 );
3334 }
3335 if flags.contains(ClipboardEventFlags::QueueInputEvent) {
3336 self.textinput.borrow().queue_input_event(
3337 self.upcast(),
3338 reaction.text,
3339 IsComposing::NotComposing,
3340 reaction.input_type,
3341 );
3342 }
3343 if !flags.is_empty() {
3344 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
3345 }
3346 } else if let Some(event) = event.downcast::<FocusEvent>() {
3347 if *event.upcast::<Event>().type_() == *"blur" {
3348 self.owner_document()
3349 .embedder_controls()
3350 .hide_embedder_control(self.upcast());
3351 }
3352 if *event.upcast::<Event>().type_() == *"focus" {
3353 self.handle_focus();
3354 }
3355 }
3356
3357 self.value_changed(can_gc);
3358 }
3359
3360 fn cloning_steps(
3362 &self,
3363 copy: &Node,
3364 maybe_doc: Option<&Document>,
3365 clone_children: CloneChildrenFlag,
3366 can_gc: CanGc,
3367 ) {
3368 if let Some(s) = self.super_type() {
3369 s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
3370 }
3371 let elem = copy.downcast::<HTMLInputElement>().unwrap();
3372 elem.value_dirty.set(self.value_dirty.get());
3373 elem.checked_changed.set(self.checked_changed.get());
3374 elem.upcast::<Element>()
3375 .set_state(ElementState::CHECKED, self.Checked());
3376 elem.textinput
3377 .borrow_mut()
3378 .set_content(self.textinput.borrow().get_content());
3379 self.value_changed(can_gc);
3380 }
3381}
3382
3383impl FormControl for HTMLInputElement {
3384 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
3385 self.form_owner.get()
3386 }
3387
3388 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
3389 self.form_owner.set(form);
3390 }
3391
3392 fn to_element(&self) -> &Element {
3393 self.upcast::<Element>()
3394 }
3395}
3396
3397impl Validatable for HTMLInputElement {
3398 fn as_element(&self) -> &Element {
3399 self.upcast()
3400 }
3401
3402 fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
3403 self.validity_state
3404 .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
3405 }
3406
3407 fn is_instance_validatable(&self) -> bool {
3408 match self.input_type() {
3415 InputType::Hidden | InputType::Button | InputType::Reset => false,
3416 _ => {
3417 !(self.upcast::<Element>().disabled_state() ||
3418 self.ReadOnly() ||
3419 is_barred_by_datalist_ancestor(self.upcast()))
3420 },
3421 }
3422 }
3423
3424 fn perform_validation(
3425 &self,
3426 validate_flags: ValidationFlags,
3427 can_gc: CanGc,
3428 ) -> ValidationFlags {
3429 let mut failed_flags = ValidationFlags::empty();
3430 let value = self.Value();
3431
3432 if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
3433 self.suffers_from_being_missing(&value)
3434 {
3435 failed_flags.insert(ValidationFlags::VALUE_MISSING);
3436 }
3437
3438 if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
3439 self.suffers_from_type_mismatch(&value)
3440 {
3441 failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
3442 }
3443
3444 if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
3445 self.suffers_from_pattern_mismatch(&value, can_gc)
3446 {
3447 failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
3448 }
3449
3450 if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
3451 self.suffers_from_bad_input(&value)
3452 {
3453 failed_flags.insert(ValidationFlags::BAD_INPUT);
3454 }
3455
3456 if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
3457 failed_flags |= self.suffers_from_length_issues(&value);
3458 }
3459
3460 if validate_flags.intersects(
3461 ValidationFlags::RANGE_UNDERFLOW |
3462 ValidationFlags::RANGE_OVERFLOW |
3463 ValidationFlags::STEP_MISMATCH,
3464 ) {
3465 failed_flags |= self.suffers_from_range_issues(&value);
3466 }
3467
3468 failed_flags & validate_flags
3469 }
3470}
3471
3472impl Activatable for HTMLInputElement {
3473 fn as_element(&self) -> &Element {
3474 self.upcast()
3475 }
3476
3477 fn is_instance_activatable(&self) -> bool {
3478 match self.input_type() {
3479 InputType::Submit | InputType::Reset | InputType::File | InputType::Image => {
3484 self.is_mutable()
3485 },
3486 InputType::Checkbox | InputType::Radio | InputType::Color => true,
3490 _ => false,
3491 }
3492 }
3493
3494 fn legacy_pre_activation_behavior(&self, can_gc: CanGc) -> Option<InputActivationState> {
3496 let ty = self.input_type();
3497 let activation_state = match ty {
3498 InputType::Checkbox => {
3499 let was_checked = self.Checked();
3500 let was_indeterminate = self.Indeterminate();
3501 self.SetIndeterminate(false);
3502 self.SetChecked(!was_checked, can_gc);
3503 Some(InputActivationState {
3504 checked: was_checked,
3505 indeterminate: was_indeterminate,
3506 checked_radio: None,
3507 old_type: InputType::Checkbox,
3508 })
3509 },
3510 InputType::Radio => {
3511 let root = self
3512 .upcast::<Node>()
3513 .GetRootNode(&GetRootNodeOptions::empty());
3514 let form_owner = self.form_owner();
3515 let checked_member = radio_group_iter(
3516 self,
3517 self.radio_group_name().as_ref(),
3518 form_owner.as_deref(),
3519 &root,
3520 )
3521 .find(|r| r.Checked());
3522 let was_checked = self.Checked();
3523 self.SetChecked(true, can_gc);
3524 Some(InputActivationState {
3525 checked: was_checked,
3526 indeterminate: false,
3527 checked_radio: checked_member.as_deref().map(DomRoot::from_ref),
3528 old_type: InputType::Radio,
3529 })
3530 },
3531 _ => None,
3532 };
3533
3534 if activation_state.is_some() {
3535 self.value_changed(can_gc);
3536 }
3537
3538 activation_state
3539 }
3540
3541 fn legacy_canceled_activation_behavior(
3543 &self,
3544 cache: Option<InputActivationState>,
3545 can_gc: CanGc,
3546 ) {
3547 let ty = self.input_type();
3549 let cache = match cache {
3550 Some(cache) => {
3551 if cache.old_type != ty {
3552 return;
3555 }
3556 cache
3557 },
3558 None => {
3559 return;
3560 },
3561 };
3562
3563 match ty {
3564 InputType::Checkbox => {
3566 self.SetIndeterminate(cache.indeterminate);
3567 self.SetChecked(cache.checked, can_gc);
3568 },
3569 InputType::Radio => {
3571 if let Some(ref o) = cache.checked_radio {
3572 let tree_root = self
3573 .upcast::<Node>()
3574 .GetRootNode(&GetRootNodeOptions::empty());
3575 if in_same_group(
3578 o,
3579 self.form_owner().as_deref(),
3580 self.radio_group_name().as_ref(),
3581 Some(&*tree_root),
3582 ) {
3583 o.SetChecked(true, can_gc);
3584 } else {
3585 self.SetChecked(false, can_gc);
3586 }
3587 } else {
3588 self.SetChecked(false, can_gc);
3589 }
3590 },
3591 _ => (),
3592 }
3593
3594 self.value_changed(can_gc);
3595 }
3596
3597 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) {
3599 match self.input_type() {
3600 InputType::Submit | InputType::Image => {
3603 if let Some(form_owner) = self.form_owner() {
3605 let document = self.owner_document();
3607
3608 if !document.is_fully_active() {
3609 return;
3610 }
3611
3612 form_owner.submit(
3615 SubmittedFrom::NotFromForm,
3616 FormSubmitterElement::Input(self),
3617 can_gc,
3618 )
3619 }
3620 },
3621 InputType::Reset => {
3622 if let Some(form_owner) = self.form_owner() {
3625 let document = self.owner_document();
3626
3627 if !document.is_fully_active() {
3629 return;
3630 }
3631
3632 form_owner.reset(ResetFrom::NotFromForm, can_gc);
3634 }
3635 },
3636 InputType::Checkbox | InputType::Radio => {
3639 if !self.upcast::<Node>().is_connected() {
3641 return;
3642 }
3643
3644 let target = self.upcast::<EventTarget>();
3645
3646 target.fire_event_with_params(
3649 atom!("input"),
3650 EventBubbles::Bubbles,
3651 EventCancelable::NotCancelable,
3652 EventComposed::Composed,
3653 can_gc,
3654 );
3655
3656 target.fire_bubbling_event(atom!("change"), can_gc);
3659 },
3660 InputType::File => {
3662 self.select_files(None);
3663 },
3664 InputType::Color => {
3666 self.show_the_picker_if_applicable();
3667 },
3668 _ => (),
3669 }
3670 }
3671}
3672
3673fn filter_from_accept(s: &DOMString) -> Vec<FilterPattern> {
3675 let mut filter = vec![];
3676 for p in split_commas(&s.str()) {
3677 let p = p.trim();
3678 if let Some('.') = p.chars().next() {
3679 filter.push(FilterPattern(p[1..].to_string()));
3680 } else if let Some(exts) = mime_guess::get_mime_extensions_str(p) {
3681 for ext in exts {
3682 filter.push(FilterPattern(ext.to_string()));
3683 }
3684 }
3685 }
3686
3687 filter
3688}
3689
3690fn round_halves_positive(n: f64) -> f64 {
3691 if n.fract() == -0.5 {
3695 n.ceil()
3696 } else {
3697 n.round()
3698 }
3699}
3700
3701fn compile_pattern(
3705 cx: SafeJSContext,
3706 pattern_str: &str,
3707 out_regex: MutableHandleObject,
3708 can_gc: CanGc,
3709) -> bool {
3710 if check_js_regex_syntax(cx, pattern_str, can_gc) {
3712 let pattern_str = format!("^(?:{})$", pattern_str);
3714 let flags = RegExpFlags {
3715 flags_: RegExpFlag_UnicodeSets,
3716 };
3717 new_js_regex(cx, &pattern_str, flags, out_regex, can_gc)
3718 } else {
3719 false
3720 }
3721}
3722
3723#[expect(unsafe_code)]
3724fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str, _can_gc: CanGc) -> bool {
3727 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3728 unsafe {
3729 rooted!(in(*cx) let mut exception = UndefinedValue());
3730
3731 let valid = CheckRegExpSyntax(
3732 *cx,
3733 pattern.as_ptr(),
3734 pattern.len(),
3735 RegExpFlags {
3736 flags_: RegExpFlag_UnicodeSets,
3737 },
3738 exception.handle_mut(),
3739 );
3740
3741 if !valid {
3742 JS_ClearPendingException(*cx);
3743 return false;
3744 }
3745
3746 exception.is_undefined()
3749 }
3750}
3751
3752#[expect(unsafe_code)]
3753pub(crate) fn new_js_regex(
3754 cx: SafeJSContext,
3755 pattern: &str,
3756 flags: RegExpFlags,
3757 mut out_regex: MutableHandleObject,
3758 _can_gc: CanGc,
3759) -> bool {
3760 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3761 unsafe {
3762 out_regex.set(NewUCRegExpObject(
3763 *cx,
3764 pattern.as_ptr(),
3765 pattern.len(),
3766 flags,
3767 ));
3768 if out_regex.is_null() {
3769 JS_ClearPendingException(*cx);
3770 return false;
3771 }
3772 }
3773 true
3774}
3775
3776#[expect(unsafe_code)]
3777fn matches_js_regex(
3778 cx: SafeJSContext,
3779 regex_obj: HandleObject,
3780 value: &str,
3781 _can_gc: CanGc,
3782) -> Result<bool, ()> {
3783 let mut value: Vec<u16> = value.encode_utf16().collect();
3784
3785 unsafe {
3786 let mut is_regex = false;
3787 assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
3788 assert!(is_regex);
3789
3790 rooted!(in(*cx) let mut rval = UndefinedValue());
3791 let mut index = 0;
3792
3793 let ok = ExecuteRegExpNoStatics(
3794 *cx,
3795 regex_obj,
3796 value.as_mut_ptr(),
3797 value.len(),
3798 &mut index,
3799 true,
3800 rval.handle_mut(),
3801 );
3802
3803 if ok {
3804 Ok(!rval.is_null())
3805 } else {
3806 JS_ClearPendingException(*cx);
3807 Err(())
3808 }
3809 }
3810}
3811
3812#[derive(MallocSizeOf)]
3816struct PendingWebDriverResponse {
3817 response_sender: IpcSender<Result<bool, ErrorStatus>>,
3819 expected_file_count: usize,
3821}
3822
3823impl PendingWebDriverResponse {
3824 fn finish(self, number_files_selected: usize) {
3825 if number_files_selected == self.expected_file_count {
3826 let _ = self.response_sender.send(Ok(false));
3827 } else {
3828 let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
3831 }
3832 }
3833}