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 self.is_textual_widget() && !self.textinput.borrow().get_content().is_empty()
1611 }
1612
1613 fn has_selection(&self) -> bool {
1614 self.textinput.borrow().has_selection()
1615 }
1616
1617 fn set_dirty_value_flag(&self, value: bool) {
1618 self.value_dirty.set(value)
1619 }
1620
1621 fn select_all(&self) {
1622 self.textinput.borrow_mut().select_all();
1623 self.upcast::<Node>().dirty(NodeDamage::Other);
1624 }
1625}
1626
1627#[allow(non_snake_case)]
1628impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1629 make_getter!(Accept, "accept");
1631
1632 make_setter!(SetAccept, "accept");
1634
1635 make_getter!(Alt, "alt");
1637
1638 make_setter!(SetAlt, "alt");
1640
1641 make_getter!(DirName, "dirname");
1643
1644 make_setter!(SetDirName, "dirname");
1646
1647 make_bool_getter!(Disabled, "disabled");
1649
1650 make_bool_setter!(SetDisabled, "disabled");
1652
1653 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1655 self.form_owner()
1656 }
1657
1658 fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1660 self.filelist.get().as_ref().cloned()
1661 }
1662
1663 fn SetFiles(&self, files: Option<&FileList>) {
1665 if self.input_type() == InputType::File && files.is_some() {
1666 self.filelist.set(files);
1667 }
1668 }
1669
1670 make_bool_getter!(DefaultChecked, "checked");
1672
1673 make_bool_setter!(SetDefaultChecked, "checked");
1675
1676 fn Checked(&self) -> bool {
1678 self.upcast::<Element>()
1679 .state()
1680 .contains(ElementState::CHECKED)
1681 }
1682
1683 fn SetChecked(&self, checked: bool, can_gc: CanGc) {
1685 self.update_checked_state(checked, true, can_gc);
1686 self.value_changed(can_gc);
1687 }
1688
1689 make_bool_getter!(ReadOnly, "readonly");
1691
1692 make_bool_setter!(SetReadOnly, "readonly");
1694
1695 make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1697
1698 make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1700
1701 fn Type(&self) -> DOMString {
1703 DOMString::from(self.input_type().as_str())
1704 }
1705
1706 make_atomic_setter!(SetType, "type");
1708
1709 fn Value(&self) -> DOMString {
1711 match self.value_mode() {
1712 ValueMode::Value => self.textinput.borrow().get_content(),
1713 ValueMode::Default => self
1714 .upcast::<Element>()
1715 .get_attribute(&ns!(), &local_name!("value"))
1716 .map_or(DOMString::from(""), |a| {
1717 DOMString::from(a.summarize().value)
1718 }),
1719 ValueMode::DefaultOn => self
1720 .upcast::<Element>()
1721 .get_attribute(&ns!(), &local_name!("value"))
1722 .map_or(DOMString::from("on"), |a| {
1723 DOMString::from(a.summarize().value)
1724 }),
1725 ValueMode::Filename => {
1726 let mut path = DOMString::from("");
1727 match self.filelist.get() {
1728 Some(ref fl) => match fl.Item(0) {
1729 Some(ref f) => {
1730 path.push_str("C:\\fakepath\\");
1731 path.push_str(&f.name().str());
1732 path
1733 },
1734 None => path,
1735 },
1736 None => path,
1737 }
1738 },
1739 }
1740 }
1741
1742 fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult {
1744 match self.value_mode() {
1745 ValueMode::Value => {
1746 {
1747 self.value_dirty.set(true);
1749
1750 self.sanitize_value(&mut value);
1752
1753 let mut textinput = self.textinput.borrow_mut();
1754
1755 if *textinput.single_line_content() != value {
1757 textinput.set_content(value);
1759
1760 textinput.clear_selection_to_limit(Direction::Forward);
1762 }
1763 }
1764
1765 self.update_placeholder_shown_state();
1769 },
1770 ValueMode::Default | ValueMode::DefaultOn => {
1771 self.upcast::<Element>()
1772 .set_string_attribute(&local_name!("value"), value, can_gc);
1773 },
1774 ValueMode::Filename => {
1775 if value.is_empty() {
1776 let window = self.owner_window();
1777 let fl = FileList::new(&window, vec![], can_gc);
1778 self.filelist.set(Some(&fl));
1779 } else {
1780 return Err(Error::InvalidState(None));
1781 }
1782 },
1783 }
1784
1785 self.value_changed(can_gc);
1786 self.upcast::<Node>().dirty(NodeDamage::Other);
1787 Ok(())
1788 }
1789
1790 make_getter!(DefaultValue, "value");
1792
1793 make_setter!(SetDefaultValue, "value");
1795
1796 make_getter!(Min, "min");
1798
1799 make_setter!(SetMin, "min");
1801
1802 fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1804 self.suggestions_source_element()
1805 }
1806
1807 #[expect(unsafe_code)]
1809 fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> {
1810 self.convert_string_to_naive_datetime(self.Value())
1811 .map(|date_time| unsafe {
1812 let time = ClippedTime {
1813 t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1814 };
1815 NonNull::new_unchecked(NewDateObject(*cx, time))
1816 })
1817 }
1818
1819 #[allow(non_snake_case)]
1821 #[expect(unsafe_code)]
1822 fn SetValueAsDate(
1823 &self,
1824 cx: SafeJSContext,
1825 value: *mut JSObject,
1826 can_gc: CanGc,
1827 ) -> ErrorResult {
1828 rooted!(in(*cx) let value = value);
1829 if !self.does_value_as_date_apply() {
1830 return Err(Error::InvalidState(None));
1831 }
1832 if value.is_null() {
1833 return self.SetValue(DOMString::from(""), can_gc);
1834 }
1835 let mut msecs: f64 = 0.0;
1836 unsafe {
1840 let mut isDate = false;
1841 if !ObjectIsDate(*cx, Handle::from(value.handle()), &mut isDate) {
1842 return Err(Error::JSFailed);
1843 }
1844 if !isDate {
1845 return Err(Error::Type("Value was not a date".to_string()));
1846 }
1847 if !DateGetMsecSinceEpoch(*cx, Handle::from(value.handle()), &mut msecs) {
1848 return Err(Error::JSFailed);
1849 }
1850 if !msecs.is_finite() {
1851 return self.SetValue(DOMString::from(""), can_gc);
1852 }
1853 }
1854
1855 let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1856 return self.SetValue(DOMString::from(""), can_gc);
1857 };
1858 self.SetValue(self.convert_datetime_to_dom_string(date_time), can_gc)
1859 }
1860
1861 fn ValueAsNumber(&self) -> f64 {
1863 self.convert_string_to_number(&self.Value().str())
1864 .unwrap_or(f64::NAN)
1865 }
1866
1867 fn SetValueAsNumber(&self, value: f64, can_gc: CanGc) -> ErrorResult {
1869 if value.is_infinite() {
1870 Err(Error::Type("value is not finite".to_string()))
1871 } else if !self.does_value_as_number_apply() {
1872 Err(Error::InvalidState(None))
1873 } else if value.is_nan() {
1874 self.SetValue(DOMString::from(""), can_gc)
1875 } else if let Some(converted) = self.convert_number_to_string(value) {
1876 self.SetValue(converted, can_gc)
1877 } else {
1878 self.SetValue(DOMString::from(""), can_gc)
1883 }
1884 }
1885
1886 make_getter!(Name, "name");
1888
1889 make_atomic_setter!(SetName, "name");
1891
1892 make_getter!(Placeholder, "placeholder");
1894
1895 make_setter!(SetPlaceholder, "placeholder");
1897
1898 make_form_action_getter!(FormAction, "formaction");
1900
1901 make_setter!(SetFormAction, "formaction");
1903
1904 make_enumerated_getter!(
1906 FormEnctype,
1907 "formenctype",
1908 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1909 invalid => "application/x-www-form-urlencoded"
1910 );
1911
1912 make_setter!(SetFormEnctype, "formenctype");
1914
1915 make_enumerated_getter!(
1917 FormMethod,
1918 "formmethod",
1919 "get" | "post" | "dialog",
1920 invalid => "get"
1921 );
1922
1923 make_setter!(SetFormMethod, "formmethod");
1925
1926 make_getter!(FormTarget, "formtarget");
1928
1929 make_setter!(SetFormTarget, "formtarget");
1931
1932 make_bool_getter!(FormNoValidate, "formnovalidate");
1934
1935 make_bool_setter!(SetFormNoValidate, "formnovalidate");
1937
1938 make_getter!(Max, "max");
1940
1941 make_setter!(SetMax, "max");
1943
1944 make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1946
1947 make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1949
1950 make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1952
1953 make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1955
1956 make_bool_getter!(Multiple, "multiple");
1958
1959 make_bool_setter!(SetMultiple, "multiple");
1961
1962 make_getter!(Pattern, "pattern");
1964
1965 make_setter!(SetPattern, "pattern");
1967
1968 make_bool_getter!(Required, "required");
1970
1971 make_bool_setter!(SetRequired, "required");
1973
1974 make_url_getter!(Src, "src");
1976
1977 make_url_setter!(SetSrc, "src");
1979
1980 make_getter!(Step, "step");
1982
1983 make_setter!(SetStep, "step");
1985
1986 fn Indeterminate(&self) -> bool {
1988 self.upcast::<Element>()
1989 .state()
1990 .contains(ElementState::INDETERMINATE)
1991 }
1992
1993 fn SetIndeterminate(&self, val: bool) {
1995 self.upcast::<Element>()
1996 .set_state(ElementState::INDETERMINATE, val)
1997 }
1998
1999 fn GetLabels(&self, can_gc: CanGc) -> Option<DomRoot<NodeList>> {
2003 if self.input_type() == InputType::Hidden {
2004 None
2005 } else {
2006 Some(self.labels_node_list.or_init(|| {
2007 NodeList::new_labels_list(
2008 self.upcast::<Node>().owner_doc().window(),
2009 self.upcast::<HTMLElement>(),
2010 can_gc,
2011 )
2012 }))
2013 }
2014 }
2015
2016 fn Select(&self) {
2018 self.selection().dom_select();
2019 }
2020
2021 fn GetSelectionStart(&self) -> Option<u32> {
2023 self.selection().dom_start()
2024 }
2025
2026 fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
2028 self.selection().set_dom_start(start)
2029 }
2030
2031 fn GetSelectionEnd(&self) -> Option<u32> {
2033 self.selection().dom_end()
2034 }
2035
2036 fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
2038 self.selection().set_dom_end(end)
2039 }
2040
2041 fn GetSelectionDirection(&self) -> Option<DOMString> {
2043 self.selection().dom_direction()
2044 }
2045
2046 fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
2048 self.selection().set_dom_direction(direction)
2049 }
2050
2051 fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
2053 self.selection().set_dom_range(start, end, direction)
2054 }
2055
2056 fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
2058 self.selection()
2059 .set_dom_range_text(replacement, None, None, Default::default())
2060 }
2061
2062 fn SetRangeText_(
2064 &self,
2065 replacement: DOMString,
2066 start: u32,
2067 end: u32,
2068 selection_mode: SelectionMode,
2069 ) -> ErrorResult {
2070 self.selection()
2071 .set_dom_range_text(replacement, Some(start), Some(end), selection_mode)
2072 }
2073
2074 fn SelectFiles(&self, paths: Vec<DOMString>) {
2077 if self.input_type() == InputType::File {
2078 self.select_files(Some(paths));
2079 }
2080 }
2081
2082 fn StepUp(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2084 self.step_up_or_down(n, StepDirection::Up, can_gc)
2085 }
2086
2087 fn StepDown(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2089 self.step_up_or_down(n, StepDirection::Down, can_gc)
2090 }
2091
2092 fn WillValidate(&self) -> bool {
2094 self.is_instance_validatable()
2095 }
2096
2097 fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
2099 self.validity_state(can_gc)
2100 }
2101
2102 fn CheckValidity(&self, can_gc: CanGc) -> bool {
2104 self.check_validity(can_gc)
2105 }
2106
2107 fn ReportValidity(&self, can_gc: CanGc) -> bool {
2109 self.report_validity(can_gc)
2110 }
2111
2112 fn ValidationMessage(&self) -> DOMString {
2114 self.validation_message()
2115 }
2116
2117 fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
2119 self.validity_state(can_gc).set_custom_error_message(error);
2120 }
2121}
2122
2123fn radio_group_iter<'a>(
2124 elem: &'a HTMLInputElement,
2125 group: Option<&'a Atom>,
2126 form: Option<&'a HTMLFormElement>,
2127 root: &'a Node,
2128) -> impl Iterator<Item = DomRoot<HTMLInputElement>> + 'a {
2129 root.traverse_preorder(ShadowIncluding::No)
2130 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2131 .filter(move |r| &**r == elem || in_same_group(r, form, group, Some(root)))
2132}
2133
2134fn broadcast_radio_checked(broadcaster: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2135 let root = broadcaster
2136 .upcast::<Node>()
2137 .GetRootNode(&GetRootNodeOptions::empty());
2138 let form = broadcaster.form_owner();
2139 for r in radio_group_iter(broadcaster, group, form.as_deref(), &root) {
2140 if broadcaster != &*r && r.Checked() {
2141 r.SetChecked(false, can_gc);
2142 }
2143 }
2144}
2145
2146fn perform_radio_group_validation(elem: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2147 let root = elem
2148 .upcast::<Node>()
2149 .GetRootNode(&GetRootNodeOptions::empty());
2150 let form = elem.form_owner();
2151 for r in radio_group_iter(elem, group, form.as_deref(), &root) {
2152 r.validity_state(can_gc)
2153 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2154 }
2155}
2156
2157fn in_same_group(
2159 other: &HTMLInputElement,
2160 owner: Option<&HTMLFormElement>,
2161 group: Option<&Atom>,
2162 tree_root: Option<&Node>,
2163) -> bool {
2164 if group.is_none() {
2165 return false;
2167 }
2168
2169 if other.input_type() != InputType::Radio ||
2170 other.form_owner().as_deref() != owner ||
2171 other.radio_group_name().as_ref() != group
2172 {
2173 return false;
2174 }
2175
2176 match tree_root {
2177 Some(tree_root) => {
2178 let other_root = other
2179 .upcast::<Node>()
2180 .GetRootNode(&GetRootNodeOptions::empty());
2181 tree_root == &*other_root
2182 },
2183 None => {
2184 true
2186 },
2187 }
2188}
2189
2190impl HTMLInputElement {
2191 fn radio_group_updated(&self, group: Option<&Atom>, can_gc: CanGc) {
2192 if self.Checked() {
2193 broadcast_radio_checked(self, group, can_gc);
2194 }
2195 }
2196
2197 pub(crate) fn form_datums(
2200 &self,
2201 submitter: Option<FormSubmitterElement>,
2202 encoding: Option<&'static Encoding>,
2203 ) -> Vec<FormDatum> {
2204 let ty = self.Type();
2208
2209 let name = self.Name();
2211 let is_submitter = match submitter {
2212 Some(FormSubmitterElement::Input(s)) => self == s,
2213 _ => false,
2214 };
2215
2216 match self.input_type() {
2217 InputType::Submit | InputType::Button | InputType::Reset if !is_submitter => {
2219 return vec![];
2220 },
2221
2222 InputType::Radio | InputType::Checkbox => {
2224 if !self.Checked() || name.is_empty() {
2225 return vec![];
2226 }
2227 },
2228
2229 InputType::File => {
2230 let mut datums = vec![];
2231
2232 let name = self.Name();
2234
2235 match self.GetFiles() {
2236 Some(fl) => {
2237 for f in fl.iter_files() {
2238 datums.push(FormDatum {
2239 ty: ty.clone(),
2240 name: name.clone(),
2241 value: FormDatumValue::File(DomRoot::from_ref(f)),
2242 });
2243 }
2244 },
2245 None => {
2246 datums.push(FormDatum {
2247 ty: ty.clone(),
2250 name: name.clone(),
2251 value: FormDatumValue::String(DOMString::from("")),
2252 })
2253 },
2254 }
2255
2256 return datums;
2257 },
2258
2259 InputType::Image => return vec![], InputType::Hidden => {
2263 if name.to_ascii_lowercase() == "_charset_" {
2264 return vec![FormDatum {
2265 ty: ty.clone(),
2266 name,
2267 value: FormDatumValue::String(match encoding {
2268 None => DOMString::from("UTF-8"),
2269 Some(enc) => DOMString::from(enc.name()),
2270 }),
2271 }];
2272 }
2273 },
2274
2275 _ => {
2277 if name.is_empty() {
2278 return vec![];
2279 }
2280 },
2281 }
2282
2283 vec![FormDatum {
2285 ty: ty.clone(),
2286 name,
2287 value: FormDatumValue::String(self.Value()),
2288 }]
2289 }
2290
2291 fn radio_group_name(&self) -> Option<Atom> {
2293 self.upcast::<Element>()
2294 .get_name()
2295 .filter(|name| !name.is_empty())
2296 }
2297
2298 fn update_checked_state(&self, checked: bool, dirty: bool, can_gc: CanGc) {
2299 self.upcast::<Element>()
2300 .set_state(ElementState::CHECKED, checked);
2301
2302 if dirty {
2303 self.checked_changed.set(true);
2304 }
2305
2306 if self.input_type() == InputType::Radio && checked {
2307 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
2308 }
2309
2310 self.upcast::<Node>().dirty(NodeDamage::Other);
2311 }
2312
2313 pub(crate) fn is_mutable(&self) -> bool {
2315 !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
2318 }
2319
2320 pub(crate) fn reset(&self, can_gc: CanGc) {
2322 match self.input_type() {
2323 InputType::Radio | InputType::Checkbox => {
2324 self.update_checked_state(self.DefaultChecked(), false, can_gc);
2325 self.checked_changed.set(false);
2326 self.value_changed(can_gc);
2327 },
2328 InputType::Image => (),
2329 _ => (),
2330 }
2331 self.textinput.borrow_mut().set_content(self.DefaultValue());
2332 self.value_dirty.set(false);
2333 self.upcast::<Node>().dirty(NodeDamage::Other);
2334 }
2335
2336 pub(crate) fn clear(&self, can_gc: CanGc) {
2339 self.value_dirty.set(false);
2341 self.checked_changed.set(false);
2342 self.textinput.borrow_mut().set_content(DOMString::from(""));
2344 self.update_checked_state(self.DefaultChecked(), false, can_gc);
2346 self.value_changed(can_gc);
2347 if self.filelist.get().is_some() {
2349 let window = self.owner_window();
2350 let filelist = FileList::new(&window, vec![], can_gc);
2351 self.filelist.set(Some(&filelist));
2352 }
2353 self.enable_sanitization();
2357 self.upcast::<Node>().dirty(NodeDamage::Other);
2358 }
2359
2360 fn update_placeholder_shown_state(&self) {
2361 if !self.input_type().is_textual_or_password() {
2362 return;
2363 }
2364
2365 let has_placeholder = !self.placeholder.borrow().is_empty();
2366 let has_value = !self.textinput.borrow().is_empty();
2367 let el = self.upcast::<Element>();
2368
2369 el.set_placeholder_shown_state(has_placeholder && !has_value);
2370 }
2371
2372 fn update_text_shadow_tree_placeholder(&self, can_gc: CanGc) {
2375 if !self.is_textual_widget() {
2376 return;
2377 }
2378
2379 let text_shadow_tree = self.text_shadow_tree(can_gc);
2380 text_shadow_tree.init_placeholder_container_if_necessary(self, can_gc);
2381
2382 let Some(ref placeholder_container) = *text_shadow_tree.placeholder_container.borrow()
2383 else {
2384 return;
2386 };
2387 let placeholder_text = self.placeholder.borrow().clone();
2388
2389 placeholder_container
2391 .upcast::<Node>()
2392 .GetFirstChild()
2393 .expect("UA widget text container without child")
2394 .downcast::<CharacterData>()
2395 .expect("First child is not a CharacterData node")
2396 .SetData(placeholder_text);
2397 }
2398
2399 pub(crate) fn select_files_for_webdriver(
2400 &self,
2401 test_paths: Vec<DOMString>,
2402 response_sender: IpcSender<Result<bool, ErrorStatus>>,
2403 ) {
2404 let mut stored_sender = self.pending_webdriver_response.borrow_mut();
2405 assert!(stored_sender.is_none());
2406
2407 *stored_sender = Some(PendingWebDriverResponse {
2408 response_sender,
2409 expected_file_count: test_paths.len(),
2410 });
2411
2412 self.select_files(Some(test_paths));
2413 }
2414
2415 pub(crate) fn select_files(&self, test_paths: Option<Vec<DOMString>>) {
2419 let current_paths = match &test_paths {
2420 Some(test_paths) => test_paths
2421 .iter()
2422 .filter_map(|path_str| PathBuf::from_str(&path_str.str()).ok())
2423 .collect(),
2424 None => Default::default(),
2427 };
2428
2429 let accept_current_paths_for_testing = test_paths.is_some();
2430 self.owner_document()
2431 .embedder_controls()
2432 .show_embedder_control(
2433 ControlElement::FileInput(DomRoot::from_ref(self)),
2434 EmbedderControlRequest::FilePicker(FilePickerRequest {
2435 origin: get_blob_origin(&self.owner_window().get_url()),
2436 current_paths,
2437 filter_patterns: filter_from_accept(&self.Accept()),
2438 allow_select_multiple: self.Multiple(),
2439 accept_current_paths_for_testing,
2440 }),
2441 None,
2442 );
2443 }
2444
2445 fn sanitize_value(&self, value: &mut DOMString) {
2447 if !self.sanitization_flag.get() {
2452 return;
2453 }
2454 match self.input_type() {
2455 InputType::Text | InputType::Search | InputType::Tel | InputType::Password => {
2456 value.strip_newlines();
2457 },
2458 InputType::Url => {
2459 value.strip_newlines();
2460 value.strip_leading_and_trailing_ascii_whitespace();
2461 },
2462 InputType::Date => {
2463 if !value.str().is_valid_date_string() {
2464 value.clear();
2465 }
2466 },
2467 InputType::Month => {
2468 if !value.str().is_valid_month_string() {
2469 value.clear();
2470 }
2471 },
2472 InputType::Week => {
2473 if !value.str().is_valid_week_string() {
2474 value.clear();
2475 }
2476 },
2477 InputType::Color => {
2478 if value.str().is_valid_simple_color_string() {
2479 value.make_ascii_lowercase();
2480 } else {
2481 *value = "#000000".into();
2482 }
2483 },
2484 InputType::Time => {
2485 if !value.str().is_valid_time_string() {
2486 value.clear();
2487 }
2488 },
2489 InputType::DatetimeLocal => {
2490 let time = value
2491 .str()
2492 .parse_local_date_time_string()
2493 .map(|date_time| date_time.to_local_date_time_string());
2494 match time {
2495 Some(normalized_string) => *value = DOMString::from_string(normalized_string),
2496 None => value.clear(),
2497 }
2498 },
2499 InputType::Number => {
2500 if !value.is_valid_floating_point_number_string() {
2501 value.clear();
2502 }
2503 },
2510 InputType::Range => {
2512 if !value.is_valid_floating_point_number_string() {
2513 *value = DOMString::from(self.default_range_value().to_string());
2514 }
2515 if let Ok(fval) = &value.parse::<f64>() {
2516 let mut fval = *fval;
2517 if let Some(max) = self.maximum() {
2520 if fval > max {
2521 fval = max;
2522 }
2523 }
2524 if let Some(min) = self.minimum() {
2525 if fval < min {
2526 fval = min;
2527 }
2528 }
2529 if let Some(allowed_value_step) = self.allowed_value_step() {
2534 let step_base = self.step_base();
2535 let steps_from_base = (fval - step_base) / allowed_value_step;
2536 if steps_from_base.fract() != 0.0 {
2537 let int_steps = round_halves_positive(steps_from_base);
2540 fval = int_steps * allowed_value_step + step_base;
2542
2543 if let Some(stepped_maximum) = self.stepped_maximum() {
2547 if fval > stepped_maximum {
2548 fval = stepped_maximum;
2549 }
2550 }
2551 if let Some(stepped_minimum) = self.stepped_minimum() {
2552 if fval < stepped_minimum {
2553 fval = stepped_minimum;
2554 }
2555 }
2556 }
2557 }
2558 *value = DOMString::from(fval.to_string());
2559 };
2560 },
2561 InputType::Email => {
2562 if !self.Multiple() {
2563 value.strip_newlines();
2564 value.strip_leading_and_trailing_ascii_whitespace();
2565 } else {
2566 let sanitized = split_commas(&value.str())
2567 .map(|token| {
2568 let mut token = DOMString::from(token.to_string());
2569 token.strip_newlines();
2570 token.strip_leading_and_trailing_ascii_whitespace();
2571 token
2572 })
2573 .join(",");
2574 value.clear();
2575 value.push_str(sanitized.as_str());
2576 }
2577 },
2578 InputType::Button |
2581 InputType::Checkbox |
2582 InputType::File |
2583 InputType::Hidden |
2584 InputType::Image |
2585 InputType::Radio |
2586 InputType::Reset |
2587 InputType::Submit => (),
2588 }
2589 }
2590
2591 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
2592 fn selection(&self) -> TextControlSelection<'_, Self> {
2593 TextControlSelection::new(self, &self.textinput)
2594 }
2595
2596 fn implicit_submission(&self, can_gc: CanGc) {
2598 let doc = self.owner_document();
2599 let node = doc.upcast::<Node>();
2600 let owner = self.form_owner();
2601 let form = match owner {
2602 None => return,
2603 Some(ref f) => f,
2604 };
2605
2606 if self.upcast::<Element>().click_in_progress() {
2607 return;
2608 }
2609 let submit_button = node
2610 .query_selector_iter(DOMString::from("input[type=submit]"))
2611 .unwrap()
2612 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2613 .find(|r| r.form_owner() == owner);
2614 match submit_button {
2615 Some(ref button) => {
2616 if button.is_instance_activatable() {
2617 button
2620 .upcast::<Node>()
2621 .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
2622 }
2623 },
2624 None => {
2625 let mut inputs = node
2626 .query_selector_iter(DOMString::from("input"))
2627 .unwrap()
2628 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2629 .filter(|input| {
2630 input.form_owner() == owner &&
2631 matches!(
2632 input.input_type(),
2633 InputType::Text |
2634 InputType::Search |
2635 InputType::Url |
2636 InputType::Tel |
2637 InputType::Email |
2638 InputType::Password |
2639 InputType::Date |
2640 InputType::Month |
2641 InputType::Week |
2642 InputType::Time |
2643 InputType::DatetimeLocal |
2644 InputType::Number
2645 )
2646 });
2647
2648 if inputs.nth(1).is_some() {
2649 return;
2651 }
2652 form.submit(
2653 SubmittedFrom::NotFromForm,
2654 FormSubmitterElement::Form(form),
2655 can_gc,
2656 );
2657 },
2658 }
2659 }
2660
2661 fn convert_string_to_number(&self, value: &str) -> Option<f64> {
2663 match self.input_type() {
2664 InputType::Date => value.parse_date_string().map(|date_time| {
2671 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2672 }),
2673 InputType::Month => value.parse_month_string().map(|date_time| {
2682 ((date_time.year() - 1970) * 12) as f64 + (date_time.month() as u8 - 1) as f64
2683 }),
2684 InputType::Week => value.parse_week_string().map(|date_time| {
2691 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2692 }),
2693 InputType::Time => value
2698 .parse_time_string()
2699 .map(|date_time| (date_time.time() - Time::MIDNIGHT).whole_milliseconds() as f64),
2700 InputType::DatetimeLocal => value.parse_local_date_time_string().map(|date_time| {
2707 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2708 }),
2709 InputType::Number | InputType::Range => parse_floating_point_number(value),
2710 _ => None,
2713 }
2714 }
2715
2716 fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
2718 match self.input_type() {
2719 InputType::Date | InputType::Week | InputType::Time | InputType::DatetimeLocal => {
2720 OffsetDateTime::from_unix_timestamp_nanos((value * 1e6) as i128)
2721 .ok()
2722 .map(|value| self.convert_datetime_to_dom_string(value))
2723 },
2724 InputType::Month => {
2725 let date = OffsetDateTime::UNIX_EPOCH;
2729 let years = (value / 12.) as i32;
2730 let year = date.year() + years;
2731
2732 let months = value as i32 - (years * 12);
2733 let months = match months.cmp(&0) {
2734 Ordering::Less => (12 - months) as u8,
2735 Ordering::Equal | Ordering::Greater => months as u8,
2736 } + 1;
2737
2738 let date = date
2739 .replace_year(year)
2740 .ok()?
2741 .replace_month(Month::try_from(months).ok()?)
2742 .ok()?;
2743 Some(self.convert_datetime_to_dom_string(date))
2744 },
2745 InputType::Number | InputType::Range => {
2746 let mut value = DOMString::from(value.to_string());
2747 value.set_best_representation_of_the_floating_point_number();
2748 Some(value)
2749 },
2750 _ => unreachable!("Should not have called convert_number_to_string for non-Date types"),
2751 }
2752 }
2753
2754 fn convert_string_to_naive_datetime(&self, value: DOMString) -> Option<OffsetDateTime> {
2758 match self.input_type() {
2759 InputType::Date => value.str().parse_date_string(),
2760 InputType::Time => value.str().parse_time_string(),
2761 InputType::Week => value.str().parse_week_string(),
2762 InputType::Month => value.str().parse_month_string(),
2763 InputType::DatetimeLocal => value.str().parse_local_date_time_string(),
2764 _ => None,
2766 }
2767 }
2768
2769 fn convert_datetime_to_dom_string(&self, value: OffsetDateTime) -> DOMString {
2773 DOMString::from_string(match self.input_type() {
2774 InputType::Date => value.to_date_string(),
2775 InputType::Month => value.to_month_string(),
2776 InputType::Week => value.to_week_string(),
2777 InputType::Time => value.to_time_string(),
2778 InputType::DatetimeLocal => value.to_local_date_time_string(),
2779 _ => {
2780 unreachable!("Should not have called convert_datetime_to_string for non-Date types")
2781 },
2782 })
2783 }
2784
2785 fn update_related_validity_states(&self, can_gc: CanGc) {
2786 match self.input_type() {
2787 InputType::Radio => {
2788 perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc)
2789 },
2790 _ => {
2791 self.validity_state(can_gc)
2792 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2793 },
2794 }
2795 }
2796
2797 fn value_changed(&self, can_gc: CanGc) {
2798 self.update_related_validity_states(can_gc);
2799 self.update_shadow_tree(can_gc);
2800 }
2801
2802 fn show_the_picker_if_applicable(&self) {
2804 if !self.is_mutable() {
2808 return;
2809 }
2810
2811 if self.input_type() == InputType::Color {
2814 let document = self.owner_document();
2815 let current_value = self.Value();
2816 let current_color = RgbColor {
2817 red: u8::from_str_radix(¤t_value.str()[1..3], 16).unwrap(),
2818 green: u8::from_str_radix(¤t_value.str()[3..5], 16).unwrap(),
2819 blue: u8::from_str_radix(¤t_value.str()[5..7], 16).unwrap(),
2820 };
2821 document.embedder_controls().show_embedder_control(
2822 ControlElement::ColorInput(DomRoot::from_ref(self)),
2823 EmbedderControlRequest::ColorPicker(current_color),
2824 None,
2825 );
2826 }
2827 }
2828
2829 pub(crate) fn handle_color_picker_response(&self, response: Option<RgbColor>, can_gc: CanGc) {
2830 let Some(selected_color) = response else {
2831 return;
2832 };
2833 let formatted_color = format!(
2834 "#{:0>2x}{:0>2x}{:0>2x}",
2835 selected_color.red, selected_color.green, selected_color.blue
2836 );
2837 let _ = self.SetValue(formatted_color.into(), can_gc);
2838 }
2839
2840 pub(crate) fn handle_file_picker_response(
2841 &self,
2842 response: Option<Vec<SelectedFile>>,
2843 can_gc: CanGc,
2844 ) {
2845 let mut files = Vec::new();
2846
2847 if let Some(pending_webdriver_reponse) = self.pending_webdriver_response.borrow_mut().take()
2848 {
2849 if self.Multiple() {
2856 if let Some(filelist) = self.filelist.get() {
2857 files = filelist.iter_files().map(|file| file.as_rooted()).collect();
2858 }
2859 }
2860
2861 let number_files_selected = response.as_ref().map(Vec::len).unwrap_or_default();
2862 pending_webdriver_reponse.finish(number_files_selected);
2863 }
2864
2865 let Some(response_files) = response else {
2866 return;
2867 };
2868
2869 let window = self.owner_window();
2870 files.extend(
2871 response_files
2872 .into_iter()
2873 .map(|file| File::new_from_selected(&window, file, can_gc)),
2874 );
2875
2876 if !self.Multiple() {
2879 files = files
2880 .pop()
2881 .map(|last_file| vec![last_file])
2882 .unwrap_or_default();
2883 }
2884
2885 self.filelist
2886 .set(Some(&FileList::new(&window, files, can_gc)));
2887
2888 let target = self.upcast::<EventTarget>();
2889 target.fire_event_with_params(
2890 atom!("input"),
2891 EventBubbles::Bubbles,
2892 EventCancelable::NotCancelable,
2893 EventComposed::Composed,
2894 can_gc,
2895 );
2896 target.fire_bubbling_event(atom!("change"), can_gc);
2897 }
2898
2899 fn handle_focus_event(&self, event: &FocusEvent) {
2900 if self.is_textual_widget() {
2904 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
2905 }
2906
2907 let event_type = event.upcast::<Event>().type_();
2908 if *event_type == *"blur" {
2909 self.owner_document()
2910 .embedder_controls()
2911 .hide_embedder_control(self.upcast());
2912 } else if *event_type == *"focus" {
2913 let Ok(input_method_type) = self.input_type().try_into() else {
2914 return;
2915 };
2916
2917 self.owner_document()
2918 .embedder_controls()
2919 .show_embedder_control(
2920 ControlElement::Ime(DomRoot::from_ref(self.upcast())),
2921 EmbedderControlRequest::InputMethod(InputMethodRequest {
2922 input_method_type,
2923 text: self.Value().to_string(),
2924 insertion_point: self.GetSelectionEnd(),
2925 multiline: false,
2926 }),
2927 None,
2928 );
2929 } else {
2930 unreachable!("Got unexpected FocusEvent {event_type:?}");
2931 }
2932 }
2933}
2934
2935impl VirtualMethods for HTMLInputElement {
2936 fn super_type(&self) -> Option<&dyn VirtualMethods> {
2937 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
2938 }
2939
2940 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
2941 let could_have_had_embedder_control = self.may_have_embedder_control();
2942
2943 self.super_type()
2944 .unwrap()
2945 .attribute_mutated(attr, mutation, can_gc);
2946
2947 match *attr.local_name() {
2948 local_name!("disabled") => {
2949 let disabled_state = match mutation {
2950 AttributeMutation::Set(None, _) => true,
2951 AttributeMutation::Set(Some(_), _) => {
2952 return;
2954 },
2955 AttributeMutation::Removed => false,
2956 };
2957 let el = self.upcast::<Element>();
2958 el.set_disabled_state(disabled_state);
2959 el.set_enabled_state(!disabled_state);
2960 el.check_ancestors_disabled_state_for_form_control();
2961
2962 if self.input_type().is_textual() {
2963 let read_write = !(self.ReadOnly() || el.disabled_state());
2964 el.set_read_write_state(read_write);
2965 }
2966
2967 el.update_sequentially_focusable_status(can_gc);
2968 },
2969 local_name!("checked") if !self.checked_changed.get() => {
2970 let checked_state = match mutation {
2971 AttributeMutation::Set(None, _) => true,
2972 AttributeMutation::Set(Some(_), _) => {
2973 return;
2975 },
2976 AttributeMutation::Removed => false,
2977 };
2978 self.update_checked_state(checked_state, false, can_gc);
2979 },
2980 local_name!("size") => {
2981 let size = mutation.new_value(attr).map(|value| value.as_uint());
2982 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
2983 },
2984 local_name!("type") => {
2985 let el = self.upcast::<Element>();
2986 match mutation {
2987 AttributeMutation::Set(..) => {
2988 let new_type = InputType::from(attr.value().as_atom());
2989
2990 let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
2992 let previously_selectable = self.selection_api_applies();
2993
2994 self.input_type.set(new_type);
2995
2996 if new_type.is_textual() {
2997 let read_write = !(self.ReadOnly() || el.disabled_state());
2998 el.set_read_write_state(read_write);
2999 } else {
3000 el.set_read_write_state(false);
3001 }
3002
3003 if new_type == InputType::File {
3004 let window = self.owner_window();
3005 let filelist = FileList::new(&window, vec![], can_gc);
3006 self.filelist.set(Some(&filelist));
3007 }
3008
3009 let new_value_mode = self.value_mode();
3010 match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
3011 (&ValueMode::Value, false, ValueMode::Default) |
3013 (&ValueMode::Value, false, ValueMode::DefaultOn) => {
3014 self.SetValue(old_idl_value, can_gc)
3015 .expect("Failed to set input value on type change to a default ValueMode.");
3016 },
3017
3018 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
3020 self.SetValue(
3021 self.upcast::<Element>()
3022 .get_attribute(&ns!(), &local_name!("value"))
3023 .map_or(DOMString::from(""), |a| {
3024 DOMString::from(a.summarize().value)
3025 }),
3026 can_gc,
3027 )
3028 .expect(
3029 "Failed to set input value on type change to ValueMode::Value.",
3030 );
3031 self.value_dirty.set(false);
3032 },
3033
3034 (_, _, ValueMode::Filename)
3036 if old_value_mode != ValueMode::Filename =>
3037 {
3038 self.SetValue(DOMString::from(""), can_gc)
3039 .expect("Failed to set input value on type change to ValueMode::Filename.");
3040 },
3041 _ => {},
3042 }
3043
3044 if new_type == InputType::Radio {
3046 self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3047 }
3048
3049 let mut textinput = self.textinput.borrow_mut();
3051 let mut value = textinput.single_line_content().clone();
3052 self.sanitize_value(&mut value);
3053 textinput.set_content(value);
3054 self.upcast::<Node>().dirty(NodeDamage::Other);
3055
3056 if !previously_selectable && self.selection_api_applies() {
3058 textinput.clear_selection_to_limit(Direction::Backward);
3059 }
3060 },
3061 AttributeMutation::Removed => {
3062 if self.input_type() == InputType::Radio {
3063 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
3064 }
3065 self.input_type.set(InputType::default());
3066 let el = self.upcast::<Element>();
3067
3068 let read_write = !(self.ReadOnly() || el.disabled_state());
3069 el.set_read_write_state(read_write);
3070 },
3071 }
3072
3073 self.update_placeholder_shown_state();
3074 self.update_text_shadow_tree_placeholder(can_gc);
3075 },
3076 local_name!("value") if !self.value_dirty.get() => {
3079 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
3080 let mut value = value.map_or(DOMString::new(), DOMString::from);
3081
3082 self.sanitize_value(&mut value);
3083 self.textinput.borrow_mut().set_content(value);
3084 self.update_placeholder_shown_state();
3085
3086 self.upcast::<Node>().dirty(NodeDamage::Other);
3087 },
3088 local_name!("name") if self.input_type() == InputType::Radio => {
3089 self.radio_group_updated(
3090 mutation.new_value(attr).as_ref().map(|name| name.as_atom()),
3091 can_gc,
3092 );
3093 },
3094 local_name!("maxlength") => match *attr.value() {
3095 AttrValue::Int(_, value) => {
3096 let mut textinput = self.textinput.borrow_mut();
3097
3098 if value < 0 {
3099 textinput.set_max_length(None);
3100 } else {
3101 textinput.set_max_length(Some(UTF16CodeUnits(value as usize)))
3102 }
3103 },
3104 _ => panic!("Expected an AttrValue::Int"),
3105 },
3106 local_name!("minlength") => match *attr.value() {
3107 AttrValue::Int(_, value) => {
3108 let mut textinput = self.textinput.borrow_mut();
3109
3110 if value < 0 {
3111 textinput.set_min_length(None);
3112 } else {
3113 textinput.set_min_length(Some(UTF16CodeUnits(value as usize)))
3114 }
3115 },
3116 _ => panic!("Expected an AttrValue::Int"),
3117 },
3118 local_name!("placeholder") => {
3119 {
3120 let mut placeholder = self.placeholder.borrow_mut();
3121 placeholder.clear();
3122 if let AttributeMutation::Set(..) = mutation {
3123 placeholder
3124 .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
3125 }
3126 }
3127 self.update_placeholder_shown_state();
3128 self.update_text_shadow_tree_placeholder(can_gc);
3129 },
3130 local_name!("readonly") => {
3131 if self.input_type().is_textual() {
3132 let el = self.upcast::<Element>();
3133 match mutation {
3134 AttributeMutation::Set(..) => {
3135 el.set_read_write_state(false);
3136 },
3137 AttributeMutation::Removed => {
3138 el.set_read_write_state(!el.disabled_state());
3139 },
3140 }
3141 }
3142 },
3143 local_name!("form") => {
3144 self.form_attribute_mutated(mutation, can_gc);
3145 },
3146 _ => {},
3147 }
3148
3149 self.value_changed(can_gc);
3150
3151 if could_have_had_embedder_control && !self.may_have_embedder_control() {
3152 self.owner_document()
3153 .embedder_controls()
3154 .hide_embedder_control(self.upcast());
3155 }
3156 }
3157
3158 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
3159 match *name {
3160 local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
3161 local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
3162 local_name!("type") => AttrValue::from_atomic(value.into()),
3163 local_name!("maxlength") => {
3164 AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
3165 },
3166 local_name!("minlength") => {
3167 AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
3168 },
3169 _ => self
3170 .super_type()
3171 .unwrap()
3172 .parse_plain_attribute(name, value),
3173 }
3174 }
3175
3176 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
3177 if let Some(s) = self.super_type() {
3178 s.bind_to_tree(context, can_gc);
3179 }
3180 self.upcast::<Element>()
3181 .check_ancestors_disabled_state_for_form_control();
3182
3183 if self.input_type() == InputType::Radio {
3184 self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3185 }
3186
3187 self.value_changed(can_gc);
3188 }
3189
3190 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
3191 let form_owner = self.form_owner();
3192
3193 self.super_type().unwrap().unbind_from_tree(context, can_gc);
3194
3195 let node = self.upcast::<Node>();
3196 let el = self.upcast::<Element>();
3197 if node
3198 .ancestors()
3199 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
3200 {
3201 el.check_ancestors_disabled_state_for_form_control();
3202 } else {
3203 el.check_disabled_attribute();
3204 }
3205
3206 if self.input_type() == InputType::Radio {
3207 let root = context.parent.GetRootNode(&GetRootNodeOptions::empty());
3208 for r in radio_group_iter(
3209 self,
3210 self.radio_group_name().as_ref(),
3211 form_owner.as_deref(),
3212 &root,
3213 ) {
3214 r.validity_state(can_gc)
3215 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3216 }
3217 }
3218
3219 self.validity_state(can_gc)
3220 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3221
3222 if self.input_type() == InputType::Color {
3223 self.owner_document()
3224 .embedder_controls()
3225 .hide_embedder_control(self.upcast());
3226 }
3227 }
3228
3229 fn handle_event(&self, event: &Event, can_gc: CanGc) {
3235 if let Some(s) = self.super_type() {
3236 s.handle_event(event, can_gc);
3237 }
3238
3239 if event.type_() == atom!("click") && !event.DefaultPrevented() {
3240 if self.input_type().is_textual_or_password() &&
3246 !self.textinput.borrow().is_empty()
3248 {
3249 if let Some(mouse_event) = event.downcast::<MouseEvent>() {
3250 if let Some(point_in_target) = mouse_event.point_in_target() {
3254 let window = self.owner_window();
3255
3256 let edit_point_index = window
3258 .text_index_query(self.upcast::<Node>(), point_in_target.to_untyped())
3259 .unwrap_or_else(|| self.textinput.borrow().char_count());
3260 self.textinput.borrow_mut().clear_selection();
3261 self.textinput
3262 .borrow_mut()
3263 .set_edit_point_index(edit_point_index);
3264 self.upcast::<Node>().dirty(NodeDamage::Other);
3265 event.PreventDefault();
3266 }
3267 }
3268 }
3269 } else if event.type_() == atom!("keydown") &&
3270 !event.DefaultPrevented() &&
3271 self.input_type().is_textual_or_password()
3272 {
3273 if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
3274 let action = self.textinput.borrow_mut().handle_keydown(keyevent);
3277 self.handle_key_reaction(action, event, can_gc);
3278 }
3279 } else if event.type_() == atom!("keypress") &&
3280 !event.DefaultPrevented() &&
3281 self.input_type().is_textual_or_password()
3282 {
3283 } else if (event.type_() == atom!("compositionstart") ||
3287 event.type_() == atom!("compositionupdate") ||
3288 event.type_() == atom!("compositionend")) &&
3289 self.input_type().is_textual_or_password()
3290 {
3291 if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
3292 if event.type_() == atom!("compositionend") {
3293 let action = self
3294 .textinput
3295 .borrow_mut()
3296 .handle_compositionend(compositionevent);
3297 self.handle_key_reaction(action, event, can_gc);
3298 self.upcast::<Node>().dirty(NodeDamage::Other);
3299 self.update_placeholder_shown_state();
3300 } else if event.type_() == atom!("compositionupdate") {
3301 let action = self
3302 .textinput
3303 .borrow_mut()
3304 .handle_compositionupdate(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!("compositionstart") {
3309 self.update_placeholder_shown_state();
3311 }
3312 event.mark_as_handled();
3313 }
3314 } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
3315 let reaction = self
3316 .textinput
3317 .borrow_mut()
3318 .handle_clipboard_event(clipboard_event);
3319 let flags = reaction.flags;
3320 if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
3321 self.owner_document().event_handler().fire_clipboard_event(
3322 None,
3323 ClipboardEventType::Change,
3324 can_gc,
3325 );
3326 }
3327 if flags.contains(ClipboardEventFlags::QueueInputEvent) {
3328 self.textinput.borrow().queue_input_event(
3329 self.upcast(),
3330 reaction.text,
3331 IsComposing::NotComposing,
3332 reaction.input_type,
3333 );
3334 }
3335 if !flags.is_empty() {
3336 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
3337 }
3338 } else if let Some(event) = event.downcast::<FocusEvent>() {
3339 self.handle_focus_event(event)
3340 }
3341
3342 self.value_changed(can_gc);
3343 }
3344
3345 fn cloning_steps(
3347 &self,
3348 copy: &Node,
3349 maybe_doc: Option<&Document>,
3350 clone_children: CloneChildrenFlag,
3351 can_gc: CanGc,
3352 ) {
3353 if let Some(s) = self.super_type() {
3354 s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
3355 }
3356 let elem = copy.downcast::<HTMLInputElement>().unwrap();
3357 elem.value_dirty.set(self.value_dirty.get());
3358 elem.checked_changed.set(self.checked_changed.get());
3359 elem.upcast::<Element>()
3360 .set_state(ElementState::CHECKED, self.Checked());
3361 elem.textinput
3362 .borrow_mut()
3363 .set_content(self.textinput.borrow().get_content());
3364 self.value_changed(can_gc);
3365 }
3366}
3367
3368impl FormControl for HTMLInputElement {
3369 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
3370 self.form_owner.get()
3371 }
3372
3373 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
3374 self.form_owner.set(form);
3375 }
3376
3377 fn to_element(&self) -> &Element {
3378 self.upcast::<Element>()
3379 }
3380}
3381
3382impl Validatable for HTMLInputElement {
3383 fn as_element(&self) -> &Element {
3384 self.upcast()
3385 }
3386
3387 fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
3388 self.validity_state
3389 .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
3390 }
3391
3392 fn is_instance_validatable(&self) -> bool {
3393 match self.input_type() {
3400 InputType::Hidden | InputType::Button | InputType::Reset => false,
3401 _ => {
3402 !(self.upcast::<Element>().disabled_state() ||
3403 self.ReadOnly() ||
3404 is_barred_by_datalist_ancestor(self.upcast()))
3405 },
3406 }
3407 }
3408
3409 fn perform_validation(
3410 &self,
3411 validate_flags: ValidationFlags,
3412 can_gc: CanGc,
3413 ) -> ValidationFlags {
3414 let mut failed_flags = ValidationFlags::empty();
3415 let value = self.Value();
3416
3417 if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
3418 self.suffers_from_being_missing(&value)
3419 {
3420 failed_flags.insert(ValidationFlags::VALUE_MISSING);
3421 }
3422
3423 if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
3424 self.suffers_from_type_mismatch(&value)
3425 {
3426 failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
3427 }
3428
3429 if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
3430 self.suffers_from_pattern_mismatch(&value, can_gc)
3431 {
3432 failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
3433 }
3434
3435 if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
3436 self.suffers_from_bad_input(&value)
3437 {
3438 failed_flags.insert(ValidationFlags::BAD_INPUT);
3439 }
3440
3441 if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
3442 failed_flags |= self.suffers_from_length_issues(&value);
3443 }
3444
3445 if validate_flags.intersects(
3446 ValidationFlags::RANGE_UNDERFLOW |
3447 ValidationFlags::RANGE_OVERFLOW |
3448 ValidationFlags::STEP_MISMATCH,
3449 ) {
3450 failed_flags |= self.suffers_from_range_issues(&value);
3451 }
3452
3453 failed_flags & validate_flags
3454 }
3455}
3456
3457impl Activatable for HTMLInputElement {
3458 fn as_element(&self) -> &Element {
3459 self.upcast()
3460 }
3461
3462 fn is_instance_activatable(&self) -> bool {
3463 match self.input_type() {
3464 InputType::Submit | InputType::Reset | InputType::File | InputType::Image => {
3469 self.is_mutable()
3470 },
3471 InputType::Checkbox | InputType::Radio | InputType::Color => true,
3475 _ => false,
3476 }
3477 }
3478
3479 fn legacy_pre_activation_behavior(&self, can_gc: CanGc) -> Option<InputActivationState> {
3481 let ty = self.input_type();
3482 let activation_state = match ty {
3483 InputType::Checkbox => {
3484 let was_checked = self.Checked();
3485 let was_indeterminate = self.Indeterminate();
3486 self.SetIndeterminate(false);
3487 self.SetChecked(!was_checked, can_gc);
3488 Some(InputActivationState {
3489 checked: was_checked,
3490 indeterminate: was_indeterminate,
3491 checked_radio: None,
3492 old_type: InputType::Checkbox,
3493 })
3494 },
3495 InputType::Radio => {
3496 let root = self
3497 .upcast::<Node>()
3498 .GetRootNode(&GetRootNodeOptions::empty());
3499 let form_owner = self.form_owner();
3500 let checked_member = radio_group_iter(
3501 self,
3502 self.radio_group_name().as_ref(),
3503 form_owner.as_deref(),
3504 &root,
3505 )
3506 .find(|r| r.Checked());
3507 let was_checked = self.Checked();
3508 self.SetChecked(true, can_gc);
3509 Some(InputActivationState {
3510 checked: was_checked,
3511 indeterminate: false,
3512 checked_radio: checked_member.as_deref().map(DomRoot::from_ref),
3513 old_type: InputType::Radio,
3514 })
3515 },
3516 _ => None,
3517 };
3518
3519 if activation_state.is_some() {
3520 self.value_changed(can_gc);
3521 }
3522
3523 activation_state
3524 }
3525
3526 fn legacy_canceled_activation_behavior(
3528 &self,
3529 cache: Option<InputActivationState>,
3530 can_gc: CanGc,
3531 ) {
3532 let ty = self.input_type();
3534 let cache = match cache {
3535 Some(cache) => {
3536 if cache.old_type != ty {
3537 return;
3540 }
3541 cache
3542 },
3543 None => {
3544 return;
3545 },
3546 };
3547
3548 match ty {
3549 InputType::Checkbox => {
3551 self.SetIndeterminate(cache.indeterminate);
3552 self.SetChecked(cache.checked, can_gc);
3553 },
3554 InputType::Radio => {
3556 if let Some(ref o) = cache.checked_radio {
3557 let tree_root = self
3558 .upcast::<Node>()
3559 .GetRootNode(&GetRootNodeOptions::empty());
3560 if in_same_group(
3563 o,
3564 self.form_owner().as_deref(),
3565 self.radio_group_name().as_ref(),
3566 Some(&*tree_root),
3567 ) {
3568 o.SetChecked(true, can_gc);
3569 } else {
3570 self.SetChecked(false, can_gc);
3571 }
3572 } else {
3573 self.SetChecked(false, can_gc);
3574 }
3575 },
3576 _ => (),
3577 }
3578
3579 self.value_changed(can_gc);
3580 }
3581
3582 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) {
3584 match self.input_type() {
3585 InputType::Submit | InputType::Image => {
3588 if let Some(form_owner) = self.form_owner() {
3590 let document = self.owner_document();
3592
3593 if !document.is_fully_active() {
3594 return;
3595 }
3596
3597 form_owner.submit(
3600 SubmittedFrom::NotFromForm,
3601 FormSubmitterElement::Input(self),
3602 can_gc,
3603 )
3604 }
3605 },
3606 InputType::Reset => {
3607 if let Some(form_owner) = self.form_owner() {
3610 let document = self.owner_document();
3611
3612 if !document.is_fully_active() {
3614 return;
3615 }
3616
3617 form_owner.reset(ResetFrom::NotFromForm, can_gc);
3619 }
3620 },
3621 InputType::Checkbox | InputType::Radio => {
3624 if !self.upcast::<Node>().is_connected() {
3626 return;
3627 }
3628
3629 let target = self.upcast::<EventTarget>();
3630
3631 target.fire_event_with_params(
3634 atom!("input"),
3635 EventBubbles::Bubbles,
3636 EventCancelable::NotCancelable,
3637 EventComposed::Composed,
3638 can_gc,
3639 );
3640
3641 target.fire_bubbling_event(atom!("change"), can_gc);
3644 },
3645 InputType::File => {
3647 self.select_files(None);
3648 },
3649 InputType::Color => {
3651 self.show_the_picker_if_applicable();
3652 },
3653 _ => (),
3654 }
3655 }
3656}
3657
3658fn filter_from_accept(s: &DOMString) -> Vec<FilterPattern> {
3660 let mut filter = vec![];
3661 for p in split_commas(&s.str()) {
3662 let p = p.trim();
3663 if let Some('.') = p.chars().next() {
3664 filter.push(FilterPattern(p[1..].to_string()));
3665 } else if let Some(exts) = mime_guess::get_mime_extensions_str(p) {
3666 for ext in exts {
3667 filter.push(FilterPattern(ext.to_string()));
3668 }
3669 }
3670 }
3671
3672 filter
3673}
3674
3675fn round_halves_positive(n: f64) -> f64 {
3676 if n.fract() == -0.5 {
3680 n.ceil()
3681 } else {
3682 n.round()
3683 }
3684}
3685
3686fn compile_pattern(
3690 cx: SafeJSContext,
3691 pattern_str: &str,
3692 out_regex: MutableHandleObject,
3693 can_gc: CanGc,
3694) -> bool {
3695 if check_js_regex_syntax(cx, pattern_str, can_gc) {
3697 let pattern_str = format!("^(?:{})$", pattern_str);
3699 let flags = RegExpFlags {
3700 flags_: RegExpFlag_UnicodeSets,
3701 };
3702 new_js_regex(cx, &pattern_str, flags, out_regex, can_gc)
3703 } else {
3704 false
3705 }
3706}
3707
3708#[expect(unsafe_code)]
3709fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str, _can_gc: CanGc) -> bool {
3712 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3713 unsafe {
3714 rooted!(in(*cx) let mut exception = UndefinedValue());
3715
3716 let valid = CheckRegExpSyntax(
3717 *cx,
3718 pattern.as_ptr(),
3719 pattern.len(),
3720 RegExpFlags {
3721 flags_: RegExpFlag_UnicodeSets,
3722 },
3723 exception.handle_mut(),
3724 );
3725
3726 if !valid {
3727 JS_ClearPendingException(*cx);
3728 return false;
3729 }
3730
3731 exception.is_undefined()
3734 }
3735}
3736
3737#[expect(unsafe_code)]
3738pub(crate) fn new_js_regex(
3739 cx: SafeJSContext,
3740 pattern: &str,
3741 flags: RegExpFlags,
3742 mut out_regex: MutableHandleObject,
3743 _can_gc: CanGc,
3744) -> bool {
3745 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3746 unsafe {
3747 out_regex.set(NewUCRegExpObject(
3748 *cx,
3749 pattern.as_ptr(),
3750 pattern.len(),
3751 flags,
3752 ));
3753 if out_regex.is_null() {
3754 JS_ClearPendingException(*cx);
3755 return false;
3756 }
3757 }
3758 true
3759}
3760
3761#[expect(unsafe_code)]
3762fn matches_js_regex(
3763 cx: SafeJSContext,
3764 regex_obj: HandleObject,
3765 value: &str,
3766 _can_gc: CanGc,
3767) -> Result<bool, ()> {
3768 let mut value: Vec<u16> = value.encode_utf16().collect();
3769
3770 unsafe {
3771 let mut is_regex = false;
3772 assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
3773 assert!(is_regex);
3774
3775 rooted!(in(*cx) let mut rval = UndefinedValue());
3776 let mut index = 0;
3777
3778 let ok = ExecuteRegExpNoStatics(
3779 *cx,
3780 regex_obj,
3781 value.as_mut_ptr(),
3782 value.len(),
3783 &mut index,
3784 true,
3785 rval.handle_mut(),
3786 );
3787
3788 if ok {
3789 Ok(!rval.is_null())
3790 } else {
3791 JS_ClearPendingException(*cx);
3792 Err(())
3793 }
3794 }
3795}
3796
3797#[derive(MallocSizeOf)]
3801struct PendingWebDriverResponse {
3802 response_sender: IpcSender<Result<bool, ErrorStatus>>,
3804 expected_file_count: usize,
3806}
3807
3808impl PendingWebDriverResponse {
3809 fn finish(self, number_files_selected: usize) {
3810 if number_files_selected == self.expected_file_count {
3811 let _ = self.response_sender.send(Ok(false));
3812 } else {
3813 let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
3816 }
3817 }
3818}