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;
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};
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::KeyReaction::{
92 DispatchInput, Nothing, RedrawSelection, TriggerDefaultAction,
93};
94use crate::textinput::Lines::Single;
95use crate::textinput::{
96 ClipboardEventReaction, Direction, SelectionDirection, TextInput, UTF8Bytes, UTF16CodeUnits,
97};
98
99const DEFAULT_SUBMIT_VALUE: &str = "Submit";
100const DEFAULT_RESET_VALUE: &str = "Reset";
101const PASSWORD_REPLACEMENT_CHAR: char = '●';
102const DEFAULT_FILE_INPUT_VALUE: &str = "No file chosen";
103
104#[derive(Clone, JSTraceable, MallocSizeOf)]
105#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
106struct InputTypeTextShadowTree {
126 inner_container: Dom<Element>,
127 text_container: Dom<Element>,
128 placeholder_container: DomRefCell<Option<Dom<Element>>>,
129}
130
131impl InputTypeTextShadowTree {
132 fn init_placeholder_container_if_necessary(&self, host: &HTMLInputElement, can_gc: CanGc) {
135 if self.placeholder_container.borrow().is_some() || host.placeholder.borrow().is_empty() {
138 return;
139 }
140
141 *self.placeholder_container.borrow_mut() = Some(
142 create_ua_widget_div_with_text_node(
143 &host.owner_document(),
144 self.inner_container.upcast::<Node>(),
145 PseudoElement::Placeholder,
146 true,
147 can_gc,
148 )
149 .as_traced(),
150 );
151 }
152}
153
154#[derive(Clone, JSTraceable, MallocSizeOf)]
155#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
156struct InputTypeColorShadowTree {
161 color_value: Dom<Element>,
162}
163
164#[derive(Clone, JSTraceable, MallocSizeOf)]
165#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
166#[non_exhaustive]
167enum ShadowTree {
168 Text(InputTypeTextShadowTree),
169 Color(InputTypeColorShadowTree),
170 }
172
173fn create_ua_widget_div_with_text_node(
176 document: &Document,
177 parent: &Node,
178 implemented_pseudo: PseudoElement,
179 as_first_child: bool,
180 can_gc: CanGc,
181) -> DomRoot<Element> {
182 let el = Element::create(
183 QualName::new(None, ns!(html), local_name!("div")),
184 None,
185 document,
186 ElementCreator::ScriptCreated,
187 CustomElementCreationMode::Asynchronous,
188 None,
189 can_gc,
190 );
191
192 parent
193 .upcast::<Node>()
194 .AppendChild(el.upcast::<Node>(), can_gc)
195 .unwrap();
196 el.upcast::<Node>()
197 .set_implemented_pseudo_element(implemented_pseudo);
198 let text_node = document.CreateTextNode("".into(), can_gc);
199
200 if !as_first_child {
201 el.upcast::<Node>()
202 .AppendChild(text_node.upcast::<Node>(), can_gc)
203 .unwrap();
204 } else {
205 el.upcast::<Node>()
206 .InsertBefore(
207 text_node.upcast::<Node>(),
208 el.upcast::<Node>().GetFirstChild().as_deref(),
209 can_gc,
210 )
211 .unwrap();
212 }
213 el
214}
215
216#[derive(Clone, Copy, Debug, Default, JSTraceable, PartialEq)]
218#[allow(dead_code)]
219#[derive(MallocSizeOf)]
220pub(crate) enum InputType {
221 Button,
223
224 Checkbox,
226
227 Color,
229
230 Date,
232
233 DatetimeLocal,
235
236 Email,
238
239 File,
241
242 Hidden,
244
245 Image,
247
248 Month,
250
251 Number,
253
254 Password,
256
257 Radio,
259
260 Range,
262
263 Reset,
265
266 Search,
268
269 Submit,
271
272 Tel,
274
275 #[default]
277 Text,
278
279 Time,
281
282 Url,
284
285 Week,
287}
288
289impl InputType {
290 pub(crate) fn is_textual(&self) -> bool {
295 matches!(
296 *self,
297 InputType::Date |
298 InputType::DatetimeLocal |
299 InputType::Email |
300 InputType::Hidden |
301 InputType::Month |
302 InputType::Number |
303 InputType::Range |
304 InputType::Search |
305 InputType::Tel |
306 InputType::Text |
307 InputType::Time |
308 InputType::Url |
309 InputType::Week
310 )
311 }
312
313 fn is_textual_or_password(&self) -> bool {
314 self.is_textual() || *self == InputType::Password
315 }
316
317 fn has_periodic_domain(&self) -> bool {
319 *self == InputType::Time
320 }
321
322 fn as_str(&self) -> &str {
323 match *self {
324 InputType::Button => "button",
325 InputType::Checkbox => "checkbox",
326 InputType::Color => "color",
327 InputType::Date => "date",
328 InputType::DatetimeLocal => "datetime-local",
329 InputType::Email => "email",
330 InputType::File => "file",
331 InputType::Hidden => "hidden",
332 InputType::Image => "image",
333 InputType::Month => "month",
334 InputType::Number => "number",
335 InputType::Password => "password",
336 InputType::Radio => "radio",
337 InputType::Range => "range",
338 InputType::Reset => "reset",
339 InputType::Search => "search",
340 InputType::Submit => "submit",
341 InputType::Tel => "tel",
342 InputType::Text => "text",
343 InputType::Time => "time",
344 InputType::Url => "url",
345 InputType::Week => "week",
346 }
347 }
348}
349
350impl TryFrom<InputType> for InputMethodType {
351 type Error = &'static str;
352
353 fn try_from(input_type: InputType) -> Result<Self, Self::Error> {
354 match input_type {
355 InputType::Color => Ok(InputMethodType::Color),
356 InputType::Date => Ok(InputMethodType::Date),
357 InputType::DatetimeLocal => Ok(InputMethodType::DatetimeLocal),
358 InputType::Email => Ok(InputMethodType::Email),
359 InputType::Month => Ok(InputMethodType::Month),
360 InputType::Number => Ok(InputMethodType::Number),
361 InputType::Password => Ok(InputMethodType::Password),
362 InputType::Search => Ok(InputMethodType::Search),
363 InputType::Tel => Ok(InputMethodType::Tel),
364 InputType::Text => Ok(InputMethodType::Text),
365 InputType::Time => Ok(InputMethodType::Time),
366 InputType::Url => Ok(InputMethodType::Url),
367 InputType::Week => Ok(InputMethodType::Week),
368 _ => Err("Input does not support IME."),
369 }
370 }
371}
372
373impl From<&Atom> for InputType {
374 fn from(value: &Atom) -> InputType {
375 match value.to_ascii_lowercase() {
376 atom!("button") => InputType::Button,
377 atom!("checkbox") => InputType::Checkbox,
378 atom!("color") => InputType::Color,
379 atom!("date") => InputType::Date,
380 atom!("datetime-local") => InputType::DatetimeLocal,
381 atom!("email") => InputType::Email,
382 atom!("file") => InputType::File,
383 atom!("hidden") => InputType::Hidden,
384 atom!("image") => InputType::Image,
385 atom!("month") => InputType::Month,
386 atom!("number") => InputType::Number,
387 atom!("password") => InputType::Password,
388 atom!("radio") => InputType::Radio,
389 atom!("range") => InputType::Range,
390 atom!("reset") => InputType::Reset,
391 atom!("search") => InputType::Search,
392 atom!("submit") => InputType::Submit,
393 atom!("tel") => InputType::Tel,
394 atom!("text") => InputType::Text,
395 atom!("time") => InputType::Time,
396 atom!("url") => InputType::Url,
397 atom!("week") => InputType::Week,
398 _ => Self::default(),
399 }
400 }
401}
402
403#[derive(Debug, PartialEq)]
404enum ValueMode {
405 Value,
407
408 Default,
410
411 DefaultOn,
413
414 Filename,
416}
417
418#[derive(Debug, PartialEq)]
419enum StepDirection {
420 Up,
421 Down,
422}
423
424#[dom_struct]
425pub(crate) struct HTMLInputElement {
426 htmlelement: HTMLElement,
427 input_type: Cell<InputType>,
428
429 checked_changed: Cell<bool>,
431 placeholder: DomRefCell<DOMString>,
432 size: Cell<u32>,
433 maxlength: Cell<i32>,
434 minlength: Cell<i32>,
435 #[no_trace]
436 textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
437 value_dirty: Cell<bool>,
439 sanitization_flag: Cell<bool>,
443
444 filelist: MutNullableDom<FileList>,
445 form_owner: MutNullableDom<HTMLFormElement>,
446 labels_node_list: MutNullableDom<NodeList>,
447 validity_state: MutNullableDom<ValidityState>,
448 shadow_tree: DomRefCell<Option<ShadowTree>>,
449 #[no_trace]
450 pending_webdriver_response: RefCell<Option<PendingWebDriverResponse>>,
451}
452
453#[derive(JSTraceable)]
454pub(crate) struct InputActivationState {
455 indeterminate: bool,
456 checked: bool,
457 checked_radio: Option<DomRoot<HTMLInputElement>>,
458 old_type: InputType,
460 }
462
463static DEFAULT_INPUT_SIZE: u32 = 20;
464static DEFAULT_MAX_LENGTH: i32 = -1;
465static DEFAULT_MIN_LENGTH: i32 = -1;
466
467#[allow(non_snake_case)]
468impl HTMLInputElement {
469 fn new_inherited(
470 local_name: LocalName,
471 prefix: Option<Prefix>,
472 document: &Document,
473 ) -> HTMLInputElement {
474 let embedder_sender = document
475 .window()
476 .as_global_scope()
477 .script_to_embedder_chan()
478 .clone();
479 HTMLInputElement {
480 htmlelement: HTMLElement::new_inherited_with_state(
481 ElementState::ENABLED | ElementState::READWRITE,
482 local_name,
483 prefix,
484 document,
485 ),
486 input_type: Cell::new(Default::default()),
487 placeholder: DomRefCell::new(DOMString::new()),
488 checked_changed: Cell::new(false),
489 maxlength: Cell::new(DEFAULT_MAX_LENGTH),
490 minlength: Cell::new(DEFAULT_MIN_LENGTH),
491 size: Cell::new(DEFAULT_INPUT_SIZE),
492 textinput: DomRefCell::new(TextInput::new(
493 Single,
494 DOMString::new(),
495 EmbedderClipboardProvider {
496 embedder_sender,
497 webview_id: document.webview_id(),
498 },
499 None,
500 None,
501 SelectionDirection::None,
502 )),
503 value_dirty: Cell::new(false),
504 sanitization_flag: Cell::new(true),
505 filelist: MutNullableDom::new(None),
506 form_owner: Default::default(),
507 labels_node_list: MutNullableDom::new(None),
508 validity_state: Default::default(),
509 shadow_tree: Default::default(),
510 pending_webdriver_response: Default::default(),
511 }
512 }
513
514 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
515 pub(crate) fn new(
516 local_name: LocalName,
517 prefix: Option<Prefix>,
518 document: &Document,
519 proto: Option<HandleObject>,
520 can_gc: CanGc,
521 ) -> DomRoot<HTMLInputElement> {
522 Node::reflect_node_with_proto(
523 Box::new(HTMLInputElement::new_inherited(
524 local_name, prefix, document,
525 )),
526 document,
527 proto,
528 can_gc,
529 )
530 }
531
532 pub(crate) fn auto_directionality(&self) -> Option<String> {
533 match self.input_type() {
534 InputType::Text | InputType::Search | InputType::Url | InputType::Email => {
535 let value: String = self.Value().to_string();
536 Some(HTMLInputElement::directionality_from_value(&value))
537 },
538 _ => None,
539 }
540 }
541
542 pub(crate) fn directionality_from_value(value: &str) -> String {
543 if HTMLInputElement::is_first_strong_character_rtl(value) {
544 "rtl".to_owned()
545 } else {
546 "ltr".to_owned()
547 }
548 }
549
550 fn is_first_strong_character_rtl(value: &str) -> bool {
551 for ch in value.chars() {
552 return match bidi_class(ch) {
553 BidiClass::L => false,
554 BidiClass::AL => true,
555 BidiClass::R => true,
556 _ => continue,
557 };
558 }
559 false
560 }
561
562 fn value_mode(&self) -> ValueMode {
565 match self.input_type() {
566 InputType::Submit |
567 InputType::Reset |
568 InputType::Button |
569 InputType::Image |
570 InputType::Hidden => ValueMode::Default,
571
572 InputType::Checkbox | InputType::Radio => ValueMode::DefaultOn,
573
574 InputType::Color |
575 InputType::Date |
576 InputType::DatetimeLocal |
577 InputType::Email |
578 InputType::Month |
579 InputType::Number |
580 InputType::Password |
581 InputType::Range |
582 InputType::Search |
583 InputType::Tel |
584 InputType::Text |
585 InputType::Time |
586 InputType::Url |
587 InputType::Week => ValueMode::Value,
588
589 InputType::File => ValueMode::Filename,
590 }
591 }
592
593 #[inline]
594 pub(crate) fn input_type(&self) -> InputType {
595 self.input_type.get()
596 }
597
598 pub(crate) fn is_nontypeable(&self) -> bool {
600 matches!(
601 self.input_type(),
602 InputType::Button |
603 InputType::Checkbox |
604 InputType::Color |
605 InputType::File |
606 InputType::Hidden |
607 InputType::Image |
608 InputType::Radio |
609 InputType::Range |
610 InputType::Reset |
611 InputType::Submit
612 )
613 }
614
615 #[inline]
616 pub(crate) fn is_submit_button(&self) -> bool {
617 let input_type = self.input_type.get();
618 input_type == InputType::Submit || input_type == InputType::Image
619 }
620
621 pub(crate) fn disable_sanitization(&self) {
622 self.sanitization_flag.set(false);
623 }
624
625 pub(crate) fn enable_sanitization(&self) {
626 self.sanitization_flag.set(true);
627 let mut textinput = self.textinput.borrow_mut();
628 let mut value = textinput.single_line_content().clone();
629 self.sanitize_value(&mut value);
630 textinput.set_content(value);
631 self.upcast::<Node>().dirty(NodeDamage::Other);
632 }
633
634 fn does_minmaxlength_apply(&self) -> bool {
635 matches!(
636 self.input_type(),
637 InputType::Text |
638 InputType::Search |
639 InputType::Url |
640 InputType::Tel |
641 InputType::Email |
642 InputType::Password
643 )
644 }
645
646 fn does_pattern_apply(&self) -> bool {
647 matches!(
648 self.input_type(),
649 InputType::Text |
650 InputType::Search |
651 InputType::Url |
652 InputType::Tel |
653 InputType::Email |
654 InputType::Password
655 )
656 }
657
658 fn does_multiple_apply(&self) -> bool {
659 self.input_type() == InputType::Email
660 }
661
662 fn does_value_as_number_apply(&self) -> bool {
665 matches!(
666 self.input_type(),
667 InputType::Date |
668 InputType::Month |
669 InputType::Week |
670 InputType::Time |
671 InputType::DatetimeLocal |
672 InputType::Number |
673 InputType::Range
674 )
675 }
676
677 fn does_value_as_date_apply(&self) -> bool {
678 matches!(
679 self.input_type(),
680 InputType::Date | InputType::Month | InputType::Week | InputType::Time
681 )
682 }
683
684 fn allowed_value_step(&self) -> Option<f64> {
686 let default_step = self.default_step()?;
689
690 let Some(attribute) = self
693 .upcast::<Element>()
694 .get_attribute(&ns!(), &local_name!("step"))
695 else {
696 return Some(default_step * self.step_scale_factor());
697 };
698
699 if attribute.value().eq_ignore_ascii_case("any") {
702 return None;
703 }
704
705 let Some(parsed_value) =
709 parse_floating_point_number(&attribute.value()).filter(|value| *value > 0.0)
710 else {
711 return Some(default_step * self.step_scale_factor());
712 };
713
714 Some(parsed_value * self.step_scale_factor())
718 }
719
720 fn minimum(&self) -> Option<f64> {
722 self.upcast::<Element>()
723 .get_attribute(&ns!(), &local_name!("min"))
724 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
725 .or_else(|| self.default_minimum())
726 }
727
728 fn maximum(&self) -> Option<f64> {
730 self.upcast::<Element>()
731 .get_attribute(&ns!(), &local_name!("max"))
732 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
733 .or_else(|| self.default_maximum())
734 }
735
736 fn stepped_minimum(&self) -> Option<f64> {
739 match (self.minimum(), self.allowed_value_step()) {
740 (Some(min), Some(allowed_step)) => {
741 let step_base = self.step_base();
742 let nsteps = (min - step_base) / allowed_step;
744 Some(step_base + (allowed_step * nsteps.ceil()))
746 },
747 (_, _) => None,
748 }
749 }
750
751 fn stepped_maximum(&self) -> Option<f64> {
754 match (self.maximum(), self.allowed_value_step()) {
755 (Some(max), Some(allowed_step)) => {
756 let step_base = self.step_base();
757 let nsteps = (max - step_base) / allowed_step;
759 Some(step_base + (allowed_step * nsteps.floor()))
761 },
762 (_, _) => None,
763 }
764 }
765
766 fn default_minimum(&self) -> Option<f64> {
768 match self.input_type() {
769 InputType::Range => Some(0.0),
770 _ => None,
771 }
772 }
773
774 fn default_maximum(&self) -> Option<f64> {
776 match self.input_type() {
777 InputType::Range => Some(100.0),
778 _ => None,
779 }
780 }
781
782 fn default_range_value(&self) -> f64 {
784 let min = self.minimum().unwrap_or(0.0);
785 let max = self.maximum().unwrap_or(100.0);
786 if max < min {
787 min
788 } else {
789 min + (max - min) * 0.5
790 }
791 }
792
793 fn default_step(&self) -> Option<f64> {
795 match self.input_type() {
796 InputType::Date => Some(1.0),
797 InputType::Month => Some(1.0),
798 InputType::Week => Some(1.0),
799 InputType::Time => Some(60.0),
800 InputType::DatetimeLocal => Some(60.0),
801 InputType::Number => Some(1.0),
802 InputType::Range => Some(1.0),
803 _ => None,
804 }
805 }
806
807 fn step_scale_factor(&self) -> f64 {
809 match self.input_type() {
810 InputType::Date => 86400000.0,
811 InputType::Month => 1.0,
812 InputType::Week => 604800000.0,
813 InputType::Time => 1000.0,
814 InputType::DatetimeLocal => 1000.0,
815 InputType::Number => 1.0,
816 InputType::Range => 1.0,
817 _ => unreachable!(),
818 }
819 }
820
821 fn step_base(&self) -> f64 {
823 if let Some(minimum) = self
827 .upcast::<Element>()
828 .get_attribute(&ns!(), &local_name!("min"))
829 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
830 {
831 return minimum;
832 }
833
834 if let Some(value) = self
838 .upcast::<Element>()
839 .get_attribute(&ns!(), &local_name!("value"))
840 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
841 {
842 return value;
843 }
844
845 if let Some(default_step_base) = self.default_step_base() {
847 return default_step_base;
848 }
849
850 0.0
852 }
853
854 fn default_step_base(&self) -> Option<f64> {
856 match self.input_type() {
857 InputType::Week => Some(-259200000.0),
858 _ => None,
859 }
860 }
861
862 fn step_up_or_down(&self, n: i32, dir: StepDirection, can_gc: CanGc) -> ErrorResult {
866 if !self.does_value_as_number_apply() {
869 return Err(Error::InvalidState(None));
870 }
871 let step_base = self.step_base();
872
873 let Some(allowed_value_step) = self.allowed_value_step() else {
875 return Err(Error::InvalidState(None));
876 };
877
878 let minimum = self.minimum();
881 let maximum = self.maximum();
882 if let (Some(min), Some(max)) = (minimum, maximum) {
883 if min > max {
884 return Ok(());
885 }
886
887 if let Some(stepped_minimum) = self.stepped_minimum() {
891 if stepped_minimum > max {
892 return Ok(());
893 }
894 }
895 }
896
897 let mut value: f64 = self
901 .convert_string_to_number(&self.Value().str())
902 .unwrap_or(0.0);
903
904 let valueBeforeStepping = value;
906
907 if (value - step_base) % allowed_value_step != 0.0 {
912 value = match dir {
913 StepDirection::Down =>
914 {
916 let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
917 intervals_from_base * allowed_value_step + step_base
918 },
919 StepDirection::Up =>
920 {
922 let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
923 intervals_from_base * allowed_value_step + step_base
924 },
925 };
926 }
927 else {
929 value += match dir {
934 StepDirection::Down => -f64::from(n) * allowed_value_step,
935 StepDirection::Up => f64::from(n) * allowed_value_step,
936 };
937 }
938
939 if let Some(min) = minimum {
943 if value < min {
944 value = self.stepped_minimum().unwrap_or(value);
945 }
946 }
947
948 if let Some(max) = maximum {
952 if value > max {
953 value = self.stepped_maximum().unwrap_or(value);
954 }
955 }
956
957 match dir {
961 StepDirection::Down => {
962 if value > valueBeforeStepping {
963 return Ok(());
964 }
965 },
966 StepDirection::Up => {
967 if value < valueBeforeStepping {
968 return Ok(());
969 }
970 },
971 }
972
973 self.SetValueAsNumber(value, can_gc)
977 }
978
979 fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
981 let list_string = self
982 .upcast::<Element>()
983 .get_string_attribute(&local_name!("list"));
984 if list_string.is_empty() {
985 return None;
986 }
987 let ancestor = self
988 .upcast::<Node>()
989 .GetRootNode(&GetRootNodeOptions::empty());
990 let first_with_id = &ancestor
991 .traverse_preorder(ShadowIncluding::No)
992 .find(|node| {
993 node.downcast::<Element>()
994 .is_some_and(|e| e.Id() == list_string)
995 });
996 first_with_id
997 .as_ref()
998 .and_then(|el| el.downcast::<HTMLDataListElement>())
999 .map(DomRoot::from_ref)
1000 }
1001
1002 fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
1004 match self.input_type() {
1005 InputType::Checkbox => self.Required() && !self.Checked(),
1007 InputType::Radio => {
1009 if self.radio_group_name().is_none() {
1010 return false;
1011 }
1012 let mut is_required = self.Required();
1013 let mut is_checked = self.Checked();
1014 let root = self
1015 .upcast::<Node>()
1016 .GetRootNode(&GetRootNodeOptions::empty());
1017 let form = self.form_owner();
1018 for other in radio_group_iter(
1019 self,
1020 self.radio_group_name().as_ref(),
1021 form.as_deref(),
1022 &root,
1023 ) {
1024 is_required = is_required || other.Required();
1025 is_checked = is_checked || other.Checked();
1026 }
1027 is_required && !is_checked
1028 },
1029 InputType::File => {
1031 self.Required() && self.filelist.get().is_none_or(|files| files.Length() == 0)
1032 },
1033 _ => {
1035 self.Required() &&
1036 self.value_mode() == ValueMode::Value &&
1037 self.is_mutable() &&
1038 value.is_empty()
1039 },
1040 }
1041 }
1042
1043 fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
1045 if value.is_empty() {
1046 return false;
1047 }
1048
1049 match self.input_type() {
1050 InputType::Url => Url::parse(&value.str()).is_err(),
1052 InputType::Email => {
1055 if self.Multiple() {
1056 !split_commas(&value.str()).all(|string| string.is_valid_email_address_string())
1057 } else {
1058 !value.str().is_valid_email_address_string()
1059 }
1060 },
1061 _ => false,
1063 }
1064 }
1065
1066 fn suffers_from_pattern_mismatch(&self, value: &DOMString, can_gc: CanGc) -> bool {
1068 let pattern_str = self.Pattern();
1071 if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
1072 return false;
1073 }
1074
1075 let cx = GlobalScope::get_cx();
1077 let _ac = enter_realm(self);
1078 rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>());
1079
1080 if compile_pattern(cx, &pattern_str.str(), pattern.handle_mut(), can_gc) {
1081 if self.Multiple() && self.does_multiple_apply() {
1082 !split_commas(&value.str())
1083 .all(|s| matches_js_regex(cx, pattern.handle(), s, can_gc).unwrap_or(true))
1084 } else {
1085 !matches_js_regex(cx, pattern.handle(), &value.str(), can_gc).unwrap_or(true)
1086 }
1087 } else {
1088 false
1090 }
1091 }
1092
1093 fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
1095 if value.is_empty() {
1096 return false;
1097 }
1098
1099 match self.input_type() {
1100 InputType::Email => {
1103 false
1107 },
1108 InputType::Date => !value.str().is_valid_date_string(),
1110 InputType::Month => !value.str().is_valid_month_string(),
1112 InputType::Week => !value.str().is_valid_week_string(),
1114 InputType::Time => !value.str().is_valid_time_string(),
1116 InputType::DatetimeLocal => !value.str().is_valid_local_date_time_string(),
1118 InputType::Number | InputType::Range => !value.is_valid_floating_point_number_string(),
1121 InputType::Color => !value.str().is_valid_simple_color_string(),
1123 _ => false,
1125 }
1126 }
1127
1128 fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
1131 let value_dirty = self.value_dirty.get();
1134 let textinput = self.textinput.borrow();
1135 let edit_by_user = !textinput.was_last_change_by_set_content();
1136
1137 if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
1138 return ValidationFlags::empty();
1139 }
1140
1141 let mut failed_flags = ValidationFlags::empty();
1142 let UTF16CodeUnits(value_len) = textinput.utf16_len();
1143 let min_length = self.MinLength();
1144 let max_length = self.MaxLength();
1145
1146 if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
1147 failed_flags.insert(ValidationFlags::TOO_SHORT);
1148 }
1149
1150 if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
1151 failed_flags.insert(ValidationFlags::TOO_LONG);
1152 }
1153
1154 failed_flags
1155 }
1156
1157 fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
1161 if value.is_empty() || !self.does_value_as_number_apply() {
1162 return ValidationFlags::empty();
1163 }
1164
1165 let Some(value_as_number) = self.convert_string_to_number(&value.str()) else {
1166 return ValidationFlags::empty();
1167 };
1168
1169 let mut failed_flags = ValidationFlags::empty();
1170 let min_value = self.minimum();
1171 let max_value = self.maximum();
1172
1173 let has_reversed_range = match (min_value, max_value) {
1175 (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
1176 _ => false,
1177 };
1178
1179 if has_reversed_range {
1180 if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
1182 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1183 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1184 }
1185 } else {
1186 if let Some(min_value) = min_value {
1188 if value_as_number < min_value {
1189 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1190 }
1191 }
1192 if let Some(max_value) = max_value {
1194 if value_as_number > max_value {
1195 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1196 }
1197 }
1198 }
1199
1200 if let Some(step) = self.allowed_value_step() {
1202 let diff = (self.step_base() - value_as_number) % step / value_as_number;
1206 if diff.abs() > 1e-12 {
1207 failed_flags.insert(ValidationFlags::STEP_MISMATCH);
1208 }
1209 }
1210
1211 failed_flags
1212 }
1213
1214 fn shadow_root(&self, can_gc: CanGc) -> DomRoot<ShadowRoot> {
1219 self.upcast::<Element>()
1220 .shadow_root()
1221 .unwrap_or_else(|| self.upcast::<Element>().attach_ua_shadow_root(true, can_gc))
1222 }
1223
1224 fn create_text_shadow_tree(&self, can_gc: CanGc) {
1225 let document = self.owner_document();
1226 let shadow_root = self.shadow_root(can_gc);
1227 Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
1228
1229 let inner_container = Element::create(
1230 QualName::new(None, ns!(html), local_name!("div")),
1231 None,
1232 &document,
1233 ElementCreator::ScriptCreated,
1234 CustomElementCreationMode::Asynchronous,
1235 None,
1236 can_gc,
1237 );
1238 shadow_root
1239 .upcast::<Node>()
1240 .AppendChild(inner_container.upcast::<Node>(), can_gc)
1241 .unwrap();
1242 inner_container
1243 .upcast::<Node>()
1244 .set_implemented_pseudo_element(PseudoElement::ServoTextControlInnerContainer);
1245
1246 let text_container = create_ua_widget_div_with_text_node(
1247 &document,
1248 inner_container.upcast::<Node>(),
1249 PseudoElement::ServoTextControlInnerEditor,
1250 false,
1251 can_gc,
1252 );
1253
1254 let _ = self
1255 .shadow_tree
1256 .borrow_mut()
1257 .insert(ShadowTree::Text(InputTypeTextShadowTree {
1258 inner_container: inner_container.as_traced(),
1259 text_container: text_container.as_traced(),
1260 placeholder_container: DomRefCell::new(None),
1261 }));
1262 }
1263
1264 fn text_shadow_tree(&self, can_gc: CanGc) -> Ref<'_, InputTypeTextShadowTree> {
1265 let has_text_shadow_tree = self
1266 .shadow_tree
1267 .borrow()
1268 .as_ref()
1269 .is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Text(_)));
1270 if !has_text_shadow_tree {
1271 self.create_text_shadow_tree(can_gc);
1272 }
1273
1274 let shadow_tree = self.shadow_tree.borrow();
1275 Ref::filter_map(shadow_tree, |shadow_tree| {
1276 let shadow_tree = shadow_tree.as_ref()?;
1277 match shadow_tree {
1278 ShadowTree::Text(text_tree) => Some(text_tree),
1279 _ => None,
1280 }
1281 })
1282 .ok()
1283 .expect("UA shadow tree was not created")
1284 }
1285
1286 fn create_color_shadow_tree(&self, can_gc: CanGc) {
1287 let document = self.owner_document();
1288 let shadow_root = self.shadow_root(can_gc);
1289 Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
1290
1291 let color_value = Element::create(
1292 QualName::new(None, ns!(html), local_name!("div")),
1293 None,
1294 &document,
1295 ElementCreator::ScriptCreated,
1296 CustomElementCreationMode::Asynchronous,
1297 None,
1298 can_gc,
1299 );
1300 shadow_root
1301 .upcast::<Node>()
1302 .AppendChild(color_value.upcast::<Node>(), can_gc)
1303 .unwrap();
1304 color_value
1305 .upcast::<Node>()
1306 .set_implemented_pseudo_element(PseudoElement::ColorSwatch);
1307
1308 let _ = self
1309 .shadow_tree
1310 .borrow_mut()
1311 .insert(ShadowTree::Color(InputTypeColorShadowTree {
1312 color_value: color_value.as_traced(),
1313 }));
1314 }
1315
1316 fn color_shadow_tree(&self, can_gc: CanGc) -> Ref<'_, InputTypeColorShadowTree> {
1323 let has_color_shadow_tree = self
1324 .shadow_tree
1325 .borrow()
1326 .as_ref()
1327 .is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Color(_)));
1328 if !has_color_shadow_tree {
1329 self.create_color_shadow_tree(can_gc);
1330 }
1331
1332 let shadow_tree = self.shadow_tree.borrow();
1333 Ref::filter_map(shadow_tree, |shadow_tree| {
1334 let shadow_tree = shadow_tree.as_ref()?;
1335 match shadow_tree {
1336 ShadowTree::Color(color_tree) => Some(color_tree),
1337 _ => None,
1338 }
1339 })
1340 .ok()
1341 .expect("UA shadow tree was not created")
1342 }
1343
1344 pub(crate) fn is_textual_widget(&self) -> bool {
1349 matches!(
1350 self.input_type(),
1351 InputType::Date |
1352 InputType::DatetimeLocal |
1353 InputType::Email |
1354 InputType::Month |
1355 InputType::Number |
1356 InputType::Password |
1357 InputType::Range |
1358 InputType::Search |
1359 InputType::Tel |
1360 InputType::Text |
1361 InputType::Time |
1362 InputType::Url |
1363 InputType::Week
1364 )
1365 }
1366
1367 fn update_textual_shadow_tree(&self, can_gc: CanGc) {
1371 debug_assert!(self.is_textual_widget());
1373
1374 let text_shadow_tree = self.text_shadow_tree(can_gc);
1375 let value = self.Value();
1376
1377 let value_text = match (value.is_empty(), self.input_type()) {
1386 (false, InputType::Password) => value
1388 .str()
1389 .chars()
1390 .map(|_| PASSWORD_REPLACEMENT_CHAR)
1391 .collect::<String>()
1392 .into(),
1393 (false, _) => value,
1394 (true, _) => "\u{200B}".into(),
1395 };
1396
1397 text_shadow_tree
1399 .text_container
1400 .upcast::<Node>()
1401 .GetFirstChild()
1402 .expect("UA widget text container without child")
1403 .downcast::<CharacterData>()
1404 .expect("First child is not a CharacterData node")
1405 .SetData(value_text);
1406 }
1407
1408 fn update_color_shadow_tree(&self, can_gc: CanGc) {
1409 debug_assert_eq!(self.input_type(), InputType::Color);
1411
1412 let color_shadow_tree = self.color_shadow_tree(can_gc);
1413 let mut value = self.Value();
1414 if value.str().is_valid_simple_color_string() {
1415 value.make_ascii_lowercase();
1416 } else {
1417 value = DOMString::from("#000000");
1418 }
1419 let style = format!("background-color: {value}");
1420 color_shadow_tree.color_value.set_string_attribute(
1421 &local_name!("style"),
1422 style.into(),
1423 can_gc,
1424 );
1425 }
1426
1427 fn update_shadow_tree(&self, can_gc: CanGc) {
1428 match self.input_type() {
1429 _ if self.is_textual_widget() => self.update_textual_shadow_tree(can_gc),
1430 InputType::Color => self.update_color_shadow_tree(can_gc),
1431 _ => {},
1432 }
1433 }
1434
1435 fn may_have_embedder_control(&self) -> bool {
1436 let el = self.upcast::<Element>();
1437 self.input_type() == InputType::Color && !el.disabled_state()
1438 }
1439}
1440
1441pub(crate) trait LayoutHTMLInputElementHelpers<'dom> {
1442 fn value_for_layout(self) -> Cow<'dom, str>;
1444 fn size_for_layout(self) -> u32;
1445 fn selection_for_layout(self) -> Option<Range<usize>>;
1446}
1447
1448#[allow(unsafe_code)]
1449impl<'dom> LayoutDom<'dom, HTMLInputElement> {
1450 fn get_raw_textinput_value(self) -> DOMString {
1451 unsafe {
1452 self.unsafe_get()
1453 .textinput
1454 .borrow_for_layout()
1455 .get_content()
1456 }
1457 }
1458 fn get_filelist(self) -> Option<LayoutDom<'dom, FileList>> {
1459 unsafe { self.unsafe_get().filelist.get_inner_as_layout() }
1460 }
1461
1462 fn input_type(self) -> InputType {
1463 self.unsafe_get().input_type.get()
1464 }
1465
1466 fn textinput_sorted_selection_offsets_range(self) -> Range<UTF8Bytes> {
1467 unsafe {
1468 self.unsafe_get()
1469 .textinput
1470 .borrow_for_layout()
1471 .sorted_selection_offsets_range()
1472 }
1473 }
1474}
1475
1476impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElement> {
1477 fn value_for_layout(self) -> Cow<'dom, str> {
1481 fn get_raw_attr_value<'dom>(
1482 input: LayoutDom<'dom, HTMLInputElement>,
1483 default: &'static str,
1484 ) -> Cow<'dom, str> {
1485 input
1486 .upcast::<Element>()
1487 .get_attr_val_for_layout(&ns!(), &local_name!("value"))
1488 .unwrap_or(default)
1489 .into()
1490 }
1491
1492 match self.input_type() {
1493 InputType::Checkbox | InputType::Radio | InputType::Image | InputType::Hidden => {
1494 "".into()
1495 },
1496 InputType::File => {
1497 let filelist = self.get_filelist();
1498 match filelist {
1499 Some(filelist) => {
1500 let length = filelist.len();
1501 if length == 0 {
1502 DEFAULT_FILE_INPUT_VALUE.into()
1503 } else if length == 1 {
1504 match filelist.file_for_layout(0) {
1505 Some(file) => file.name().to_string().into(),
1506 None => DEFAULT_FILE_INPUT_VALUE.into(),
1507 }
1508 } else {
1509 format!("{} files", length).into()
1510 }
1511 },
1512 None => DEFAULT_FILE_INPUT_VALUE.into(),
1513 }
1514 },
1515 InputType::Button => get_raw_attr_value(self, ""),
1516 InputType::Submit => get_raw_attr_value(self, DEFAULT_SUBMIT_VALUE),
1517 InputType::Reset => get_raw_attr_value(self, DEFAULT_RESET_VALUE),
1518 InputType::Range => "".into(),
1520 _ => {
1521 unreachable!("Input with shadow tree should use internal shadow tree for layout");
1522 },
1523 }
1524 }
1525
1526 fn size_for_layout(self) -> u32 {
1536 self.unsafe_get().size.get()
1537 }
1538
1539 fn selection_for_layout(self) -> Option<Range<usize>> {
1540 if !self.upcast::<Element>().focus_state() {
1541 return None;
1542 }
1543
1544 let sorted_selection_offsets_range = self.textinput_sorted_selection_offsets_range();
1545
1546 match self.input_type() {
1547 InputType::Password => {
1548 let text = self.get_raw_textinput_value();
1549 let sel = UTF8Bytes::unwrap_range(sorted_selection_offsets_range);
1550
1551 let char_start = text.str()[..sel.start].chars().count();
1553 let char_end = char_start + text.str()[sel].chars().count();
1554
1555 let bytes_per_char = PASSWORD_REPLACEMENT_CHAR.len_utf8();
1556 Some(char_start * bytes_per_char..char_end * bytes_per_char)
1557 },
1558 input_type if input_type.is_textual() => {
1559 Some(UTF8Bytes::unwrap_range(sorted_selection_offsets_range))
1560 },
1561 _ => None,
1562 }
1563 }
1564}
1565
1566impl TextControlElement for HTMLInputElement {
1567 fn selection_api_applies(&self) -> bool {
1569 matches!(
1570 self.input_type(),
1571 InputType::Text |
1572 InputType::Search |
1573 InputType::Url |
1574 InputType::Tel |
1575 InputType::Password
1576 )
1577 }
1578
1579 fn has_selectable_text(&self) -> bool {
1587 match self.input_type() {
1588 InputType::Text |
1589 InputType::Search |
1590 InputType::Url |
1591 InputType::Tel |
1592 InputType::Password |
1593 InputType::Email |
1594 InputType::Date |
1595 InputType::Month |
1596 InputType::Week |
1597 InputType::Time |
1598 InputType::DatetimeLocal |
1599 InputType::Number => true,
1600
1601 InputType::Button |
1602 InputType::Checkbox |
1603 InputType::Color |
1604 InputType::File |
1605 InputType::Hidden |
1606 InputType::Image |
1607 InputType::Radio |
1608 InputType::Range |
1609 InputType::Reset |
1610 InputType::Submit => false,
1611 }
1612 }
1613
1614 fn set_dirty_value_flag(&self, value: bool) {
1615 self.value_dirty.set(value)
1616 }
1617}
1618
1619#[allow(non_snake_case)]
1620impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1621 make_getter!(Accept, "accept");
1623
1624 make_setter!(SetAccept, "accept");
1626
1627 make_getter!(Alt, "alt");
1629
1630 make_setter!(SetAlt, "alt");
1632
1633 make_getter!(DirName, "dirname");
1635
1636 make_setter!(SetDirName, "dirname");
1638
1639 make_bool_getter!(Disabled, "disabled");
1641
1642 make_bool_setter!(SetDisabled, "disabled");
1644
1645 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1647 self.form_owner()
1648 }
1649
1650 fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1652 self.filelist.get().as_ref().cloned()
1653 }
1654
1655 fn SetFiles(&self, files: Option<&FileList>) {
1657 if self.input_type() == InputType::File && files.is_some() {
1658 self.filelist.set(files);
1659 }
1660 }
1661
1662 make_bool_getter!(DefaultChecked, "checked");
1664
1665 make_bool_setter!(SetDefaultChecked, "checked");
1667
1668 fn Checked(&self) -> bool {
1670 self.upcast::<Element>()
1671 .state()
1672 .contains(ElementState::CHECKED)
1673 }
1674
1675 fn SetChecked(&self, checked: bool, can_gc: CanGc) {
1677 self.update_checked_state(checked, true, can_gc);
1678 self.value_changed(can_gc);
1679 }
1680
1681 make_bool_getter!(ReadOnly, "readonly");
1683
1684 make_bool_setter!(SetReadOnly, "readonly");
1686
1687 make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1689
1690 make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1692
1693 fn Type(&self) -> DOMString {
1695 DOMString::from(self.input_type().as_str())
1696 }
1697
1698 make_atomic_setter!(SetType, "type");
1700
1701 fn Value(&self) -> DOMString {
1703 match self.value_mode() {
1704 ValueMode::Value => self.textinput.borrow().get_content(),
1705 ValueMode::Default => self
1706 .upcast::<Element>()
1707 .get_attribute(&ns!(), &local_name!("value"))
1708 .map_or(DOMString::from(""), |a| {
1709 DOMString::from(a.summarize().value)
1710 }),
1711 ValueMode::DefaultOn => self
1712 .upcast::<Element>()
1713 .get_attribute(&ns!(), &local_name!("value"))
1714 .map_or(DOMString::from("on"), |a| {
1715 DOMString::from(a.summarize().value)
1716 }),
1717 ValueMode::Filename => {
1718 let mut path = DOMString::from("");
1719 match self.filelist.get() {
1720 Some(ref fl) => match fl.Item(0) {
1721 Some(ref f) => {
1722 path.push_str("C:\\fakepath\\");
1723 path.push_str(&f.name().str());
1724 path
1725 },
1726 None => path,
1727 },
1728 None => path,
1729 }
1730 },
1731 }
1732 }
1733
1734 fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult {
1736 match self.value_mode() {
1737 ValueMode::Value => {
1738 {
1739 self.value_dirty.set(true);
1741
1742 self.sanitize_value(&mut value);
1744
1745 let mut textinput = self.textinput.borrow_mut();
1746
1747 if *textinput.single_line_content() != value {
1749 textinput.set_content(value);
1751
1752 textinput.clear_selection_to_limit(Direction::Forward);
1754 }
1755 }
1756
1757 self.update_placeholder_shown_state();
1761 },
1762 ValueMode::Default | ValueMode::DefaultOn => {
1763 self.upcast::<Element>()
1764 .set_string_attribute(&local_name!("value"), value, can_gc);
1765 },
1766 ValueMode::Filename => {
1767 if value.is_empty() {
1768 let window = self.owner_window();
1769 let fl = FileList::new(&window, vec![], can_gc);
1770 self.filelist.set(Some(&fl));
1771 } else {
1772 return Err(Error::InvalidState(None));
1773 }
1774 },
1775 }
1776
1777 self.value_changed(can_gc);
1778 self.upcast::<Node>().dirty(NodeDamage::Other);
1779 Ok(())
1780 }
1781
1782 make_getter!(DefaultValue, "value");
1784
1785 make_setter!(SetDefaultValue, "value");
1787
1788 make_getter!(Min, "min");
1790
1791 make_setter!(SetMin, "min");
1793
1794 fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1796 self.suggestions_source_element()
1797 }
1798
1799 #[allow(unsafe_code)]
1801 fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> {
1802 self.convert_string_to_naive_datetime(self.Value())
1803 .map(|date_time| unsafe {
1804 let time = ClippedTime {
1805 t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1806 };
1807 NonNull::new_unchecked(NewDateObject(*cx, time))
1808 })
1809 }
1810
1811 #[allow(unsafe_code, non_snake_case)]
1813 fn SetValueAsDate(
1814 &self,
1815 cx: SafeJSContext,
1816 value: *mut JSObject,
1817 can_gc: CanGc,
1818 ) -> ErrorResult {
1819 rooted!(in(*cx) let value = value);
1820 if !self.does_value_as_date_apply() {
1821 return Err(Error::InvalidState(None));
1822 }
1823 if value.is_null() {
1824 return self.SetValue(DOMString::from(""), can_gc);
1825 }
1826 let mut msecs: f64 = 0.0;
1827 unsafe {
1831 let mut isDate = false;
1832 if !ObjectIsDate(*cx, Handle::from(value.handle()), &mut isDate) {
1833 return Err(Error::JSFailed);
1834 }
1835 if !isDate {
1836 return Err(Error::Type("Value was not a date".to_string()));
1837 }
1838 if !DateGetMsecSinceEpoch(*cx, Handle::from(value.handle()), &mut msecs) {
1839 return Err(Error::JSFailed);
1840 }
1841 if !msecs.is_finite() {
1842 return self.SetValue(DOMString::from(""), can_gc);
1843 }
1844 }
1845
1846 let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1847 return self.SetValue(DOMString::from(""), can_gc);
1848 };
1849 self.SetValue(self.convert_datetime_to_dom_string(date_time), can_gc)
1850 }
1851
1852 fn ValueAsNumber(&self) -> f64 {
1854 self.convert_string_to_number(&self.Value().str())
1855 .unwrap_or(f64::NAN)
1856 }
1857
1858 fn SetValueAsNumber(&self, value: f64, can_gc: CanGc) -> ErrorResult {
1860 if value.is_infinite() {
1861 Err(Error::Type("value is not finite".to_string()))
1862 } else if !self.does_value_as_number_apply() {
1863 Err(Error::InvalidState(None))
1864 } else if value.is_nan() {
1865 self.SetValue(DOMString::from(""), can_gc)
1866 } else if let Some(converted) = self.convert_number_to_string(value) {
1867 self.SetValue(converted, can_gc)
1868 } else {
1869 self.SetValue(DOMString::from(""), can_gc)
1874 }
1875 }
1876
1877 make_getter!(Name, "name");
1879
1880 make_atomic_setter!(SetName, "name");
1882
1883 make_getter!(Placeholder, "placeholder");
1885
1886 make_setter!(SetPlaceholder, "placeholder");
1888
1889 make_form_action_getter!(FormAction, "formaction");
1891
1892 make_setter!(SetFormAction, "formaction");
1894
1895 make_enumerated_getter!(
1897 FormEnctype,
1898 "formenctype",
1899 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1900 invalid => "application/x-www-form-urlencoded"
1901 );
1902
1903 make_setter!(SetFormEnctype, "formenctype");
1905
1906 make_enumerated_getter!(
1908 FormMethod,
1909 "formmethod",
1910 "get" | "post" | "dialog",
1911 invalid => "get"
1912 );
1913
1914 make_setter!(SetFormMethod, "formmethod");
1916
1917 make_getter!(FormTarget, "formtarget");
1919
1920 make_setter!(SetFormTarget, "formtarget");
1922
1923 make_bool_getter!(FormNoValidate, "formnovalidate");
1925
1926 make_bool_setter!(SetFormNoValidate, "formnovalidate");
1928
1929 make_getter!(Max, "max");
1931
1932 make_setter!(SetMax, "max");
1934
1935 make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1937
1938 make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1940
1941 make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1943
1944 make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1946
1947 make_bool_getter!(Multiple, "multiple");
1949
1950 make_bool_setter!(SetMultiple, "multiple");
1952
1953 make_getter!(Pattern, "pattern");
1955
1956 make_setter!(SetPattern, "pattern");
1958
1959 make_bool_getter!(Required, "required");
1961
1962 make_bool_setter!(SetRequired, "required");
1964
1965 make_url_getter!(Src, "src");
1967
1968 make_url_setter!(SetSrc, "src");
1970
1971 make_getter!(Step, "step");
1973
1974 make_setter!(SetStep, "step");
1976
1977 fn Indeterminate(&self) -> bool {
1979 self.upcast::<Element>()
1980 .state()
1981 .contains(ElementState::INDETERMINATE)
1982 }
1983
1984 fn SetIndeterminate(&self, val: bool) {
1986 self.upcast::<Element>()
1987 .set_state(ElementState::INDETERMINATE, val)
1988 }
1989
1990 fn GetLabels(&self, can_gc: CanGc) -> Option<DomRoot<NodeList>> {
1994 if self.input_type() == InputType::Hidden {
1995 None
1996 } else {
1997 Some(self.labels_node_list.or_init(|| {
1998 NodeList::new_labels_list(
1999 self.upcast::<Node>().owner_doc().window(),
2000 self.upcast::<HTMLElement>(),
2001 can_gc,
2002 )
2003 }))
2004 }
2005 }
2006
2007 fn Select(&self) {
2009 self.selection().dom_select();
2010 }
2011
2012 fn GetSelectionStart(&self) -> Option<u32> {
2014 self.selection().dom_start()
2015 }
2016
2017 fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
2019 self.selection().set_dom_start(start)
2020 }
2021
2022 fn GetSelectionEnd(&self) -> Option<u32> {
2024 self.selection().dom_end()
2025 }
2026
2027 fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
2029 self.selection().set_dom_end(end)
2030 }
2031
2032 fn GetSelectionDirection(&self) -> Option<DOMString> {
2034 self.selection().dom_direction()
2035 }
2036
2037 fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
2039 self.selection().set_dom_direction(direction)
2040 }
2041
2042 fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
2044 self.selection().set_dom_range(start, end, direction)
2045 }
2046
2047 fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
2049 self.selection()
2050 .set_dom_range_text(replacement, None, None, Default::default())
2051 }
2052
2053 fn SetRangeText_(
2055 &self,
2056 replacement: DOMString,
2057 start: u32,
2058 end: u32,
2059 selection_mode: SelectionMode,
2060 ) -> ErrorResult {
2061 self.selection()
2062 .set_dom_range_text(replacement, Some(start), Some(end), selection_mode)
2063 }
2064
2065 fn SelectFiles(&self, paths: Vec<DOMString>) {
2068 if self.input_type() == InputType::File {
2069 self.select_files(Some(paths));
2070 }
2071 }
2072
2073 fn StepUp(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2075 self.step_up_or_down(n, StepDirection::Up, can_gc)
2076 }
2077
2078 fn StepDown(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2080 self.step_up_or_down(n, StepDirection::Down, can_gc)
2081 }
2082
2083 fn WillValidate(&self) -> bool {
2085 self.is_instance_validatable()
2086 }
2087
2088 fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
2090 self.validity_state(can_gc)
2091 }
2092
2093 fn CheckValidity(&self, can_gc: CanGc) -> bool {
2095 self.check_validity(can_gc)
2096 }
2097
2098 fn ReportValidity(&self, can_gc: CanGc) -> bool {
2100 self.report_validity(can_gc)
2101 }
2102
2103 fn ValidationMessage(&self) -> DOMString {
2105 self.validation_message()
2106 }
2107
2108 fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
2110 self.validity_state(can_gc).set_custom_error_message(error);
2111 }
2112}
2113
2114fn radio_group_iter<'a>(
2115 elem: &'a HTMLInputElement,
2116 group: Option<&'a Atom>,
2117 form: Option<&'a HTMLFormElement>,
2118 root: &'a Node,
2119) -> impl Iterator<Item = DomRoot<HTMLInputElement>> + 'a {
2120 root.traverse_preorder(ShadowIncluding::No)
2121 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2122 .filter(move |r| &**r == elem || in_same_group(r, form, group, Some(root)))
2123}
2124
2125fn broadcast_radio_checked(broadcaster: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2126 let root = broadcaster
2127 .upcast::<Node>()
2128 .GetRootNode(&GetRootNodeOptions::empty());
2129 let form = broadcaster.form_owner();
2130 for r in radio_group_iter(broadcaster, group, form.as_deref(), &root) {
2131 if broadcaster != &*r && r.Checked() {
2132 r.SetChecked(false, can_gc);
2133 }
2134 }
2135}
2136
2137fn perform_radio_group_validation(elem: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2138 let root = elem
2139 .upcast::<Node>()
2140 .GetRootNode(&GetRootNodeOptions::empty());
2141 let form = elem.form_owner();
2142 for r in radio_group_iter(elem, group, form.as_deref(), &root) {
2143 r.validity_state(can_gc)
2144 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2145 }
2146}
2147
2148fn in_same_group(
2150 other: &HTMLInputElement,
2151 owner: Option<&HTMLFormElement>,
2152 group: Option<&Atom>,
2153 tree_root: Option<&Node>,
2154) -> bool {
2155 if group.is_none() {
2156 return false;
2158 }
2159
2160 if other.input_type() != InputType::Radio ||
2161 other.form_owner().as_deref() != owner ||
2162 other.radio_group_name().as_ref() != group
2163 {
2164 return false;
2165 }
2166
2167 match tree_root {
2168 Some(tree_root) => {
2169 let other_root = other
2170 .upcast::<Node>()
2171 .GetRootNode(&GetRootNodeOptions::empty());
2172 tree_root == &*other_root
2173 },
2174 None => {
2175 true
2177 },
2178 }
2179}
2180
2181impl HTMLInputElement {
2182 fn radio_group_updated(&self, group: Option<&Atom>, can_gc: CanGc) {
2183 if self.Checked() {
2184 broadcast_radio_checked(self, group, can_gc);
2185 }
2186 }
2187
2188 pub(crate) fn form_datums(
2191 &self,
2192 submitter: Option<FormSubmitterElement>,
2193 encoding: Option<&'static Encoding>,
2194 ) -> Vec<FormDatum> {
2195 let ty = self.Type();
2199
2200 let name = self.Name();
2202 let is_submitter = match submitter {
2203 Some(FormSubmitterElement::Input(s)) => self == s,
2204 _ => false,
2205 };
2206
2207 match self.input_type() {
2208 InputType::Submit | InputType::Button | InputType::Reset if !is_submitter => {
2210 return vec![];
2211 },
2212
2213 InputType::Radio | InputType::Checkbox => {
2215 if !self.Checked() || name.is_empty() {
2216 return vec![];
2217 }
2218 },
2219
2220 InputType::File => {
2221 let mut datums = vec![];
2222
2223 let name = self.Name();
2225
2226 match self.GetFiles() {
2227 Some(fl) => {
2228 for f in fl.iter_files() {
2229 datums.push(FormDatum {
2230 ty: ty.clone(),
2231 name: name.clone(),
2232 value: FormDatumValue::File(DomRoot::from_ref(f)),
2233 });
2234 }
2235 },
2236 None => {
2237 datums.push(FormDatum {
2238 ty: ty.clone(),
2241 name: name.clone(),
2242 value: FormDatumValue::String(DOMString::from("")),
2243 })
2244 },
2245 }
2246
2247 return datums;
2248 },
2249
2250 InputType::Image => return vec![], InputType::Hidden => {
2254 if name.to_ascii_lowercase() == "_charset_" {
2255 return vec![FormDatum {
2256 ty: ty.clone(),
2257 name,
2258 value: FormDatumValue::String(match encoding {
2259 None => DOMString::from("UTF-8"),
2260 Some(enc) => DOMString::from(enc.name()),
2261 }),
2262 }];
2263 }
2264 },
2265
2266 _ => {
2268 if name.is_empty() {
2269 return vec![];
2270 }
2271 },
2272 }
2273
2274 vec![FormDatum {
2276 ty: ty.clone(),
2277 name,
2278 value: FormDatumValue::String(self.Value()),
2279 }]
2280 }
2281
2282 fn radio_group_name(&self) -> Option<Atom> {
2284 self.upcast::<Element>()
2285 .get_name()
2286 .filter(|name| !name.is_empty())
2287 }
2288
2289 fn update_checked_state(&self, checked: bool, dirty: bool, can_gc: CanGc) {
2290 self.upcast::<Element>()
2291 .set_state(ElementState::CHECKED, checked);
2292
2293 if dirty {
2294 self.checked_changed.set(true);
2295 }
2296
2297 if self.input_type() == InputType::Radio && checked {
2298 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
2299 }
2300
2301 self.upcast::<Node>().dirty(NodeDamage::Other);
2302 }
2303
2304 pub(crate) fn is_mutable(&self) -> bool {
2306 !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
2309 }
2310
2311 pub(crate) fn reset(&self, can_gc: CanGc) {
2313 match self.input_type() {
2314 InputType::Radio | InputType::Checkbox => {
2315 self.update_checked_state(self.DefaultChecked(), false, can_gc);
2316 self.checked_changed.set(false);
2317 self.value_changed(can_gc);
2318 },
2319 InputType::Image => (),
2320 _ => (),
2321 }
2322 self.textinput.borrow_mut().set_content(self.DefaultValue());
2323 self.value_dirty.set(false);
2324 self.upcast::<Node>().dirty(NodeDamage::Other);
2325 }
2326
2327 pub(crate) fn clear(&self, can_gc: CanGc) {
2330 self.value_dirty.set(false);
2332 self.checked_changed.set(false);
2333 self.textinput.borrow_mut().set_content(DOMString::from(""));
2335 self.update_checked_state(self.DefaultChecked(), false, can_gc);
2337 self.value_changed(can_gc);
2338 if self.filelist.get().is_some() {
2340 let window = self.owner_window();
2341 let filelist = FileList::new(&window, vec![], can_gc);
2342 self.filelist.set(Some(&filelist));
2343 }
2344 self.enable_sanitization();
2348 self.upcast::<Node>().dirty(NodeDamage::Other);
2349 }
2350
2351 fn update_placeholder_shown_state(&self) {
2352 if !self.input_type().is_textual_or_password() {
2353 return;
2354 }
2355
2356 let has_placeholder = !self.placeholder.borrow().is_empty();
2357 let has_value = !self.textinput.borrow().is_empty();
2358 let el = self.upcast::<Element>();
2359
2360 el.set_placeholder_shown_state(has_placeholder && !has_value);
2361 }
2362
2363 fn update_text_shadow_tree_placeholder(&self, can_gc: CanGc) {
2366 if !self.is_textual_widget() {
2367 return;
2368 }
2369
2370 let text_shadow_tree = self.text_shadow_tree(can_gc);
2371 text_shadow_tree.init_placeholder_container_if_necessary(self, can_gc);
2372
2373 let Some(ref placeholder_container) = *text_shadow_tree.placeholder_container.borrow()
2374 else {
2375 return;
2377 };
2378 let placeholder_text = self.placeholder.borrow().clone();
2379
2380 placeholder_container
2382 .upcast::<Node>()
2383 .GetFirstChild()
2384 .expect("UA widget text container without child")
2385 .downcast::<CharacterData>()
2386 .expect("First child is not a CharacterData node")
2387 .SetData(placeholder_text);
2388 }
2389
2390 pub(crate) fn select_files_for_webdriver(
2391 &self,
2392 test_paths: Vec<DOMString>,
2393 response_sender: IpcSender<Result<bool, ErrorStatus>>,
2394 ) {
2395 let mut stored_sender = self.pending_webdriver_response.borrow_mut();
2396 assert!(stored_sender.is_none());
2397
2398 *stored_sender = Some(PendingWebDriverResponse {
2399 response_sender,
2400 expected_file_count: test_paths.len(),
2401 });
2402
2403 self.select_files(Some(test_paths));
2404 }
2405
2406 pub(crate) fn select_files(&self, test_paths: Option<Vec<DOMString>>) {
2410 let current_paths = match &test_paths {
2411 Some(test_paths) => test_paths
2412 .iter()
2413 .filter_map(|path_str| PathBuf::from_str(&path_str.str()).ok())
2414 .collect(),
2415 None => Default::default(),
2418 };
2419
2420 let accept_current_paths_for_testing = test_paths.is_some();
2421 self.owner_document()
2422 .embedder_controls()
2423 .show_embedder_control(
2424 ControlElement::FileInput(DomRoot::from_ref(self)),
2425 EmbedderControlRequest::FilePicker(FilePickerRequest {
2426 origin: get_blob_origin(&self.owner_window().get_url()),
2427 current_paths,
2428 filter_patterns: filter_from_accept(&self.Accept()),
2429 allow_select_multiple: self.Multiple(),
2430 accept_current_paths_for_testing,
2431 }),
2432 );
2433 }
2434
2435 fn sanitize_value(&self, value: &mut DOMString) {
2437 if !self.sanitization_flag.get() {
2442 return;
2443 }
2444 match self.input_type() {
2445 InputType::Text | InputType::Search | InputType::Tel | InputType::Password => {
2446 value.strip_newlines();
2447 },
2448 InputType::Url => {
2449 value.strip_newlines();
2450 value.strip_leading_and_trailing_ascii_whitespace();
2451 },
2452 InputType::Date => {
2453 if !value.str().is_valid_date_string() {
2454 value.clear();
2455 }
2456 },
2457 InputType::Month => {
2458 if !value.str().is_valid_month_string() {
2459 value.clear();
2460 }
2461 },
2462 InputType::Week => {
2463 if !value.str().is_valid_week_string() {
2464 value.clear();
2465 }
2466 },
2467 InputType::Color => {
2468 if value.str().is_valid_simple_color_string() {
2469 value.make_ascii_lowercase();
2470 } else {
2471 *value = "#000000".into();
2472 }
2473 },
2474 InputType::Time => {
2475 if !value.str().is_valid_time_string() {
2476 value.clear();
2477 }
2478 },
2479 InputType::DatetimeLocal => {
2480 let time = value
2481 .str()
2482 .parse_local_date_time_string()
2483 .map(|date_time| date_time.to_local_date_time_string());
2484 match time {
2485 Some(normalized_string) => *value = DOMString::from_string(normalized_string),
2486 None => value.clear(),
2487 }
2488 },
2489 InputType::Number => {
2490 if !value.is_valid_floating_point_number_string() {
2491 value.clear();
2492 }
2493 },
2500 InputType::Range => {
2502 if !value.is_valid_floating_point_number_string() {
2503 *value = DOMString::from(self.default_range_value().to_string());
2504 }
2505 if let Ok(fval) = &value.parse::<f64>() {
2506 let mut fval = *fval;
2507 if let Some(max) = self.maximum() {
2510 if fval > max {
2511 fval = max;
2512 }
2513 }
2514 if let Some(min) = self.minimum() {
2515 if fval < min {
2516 fval = min;
2517 }
2518 }
2519 if let Some(allowed_value_step) = self.allowed_value_step() {
2524 let step_base = self.step_base();
2525 let steps_from_base = (fval - step_base) / allowed_value_step;
2526 if steps_from_base.fract() != 0.0 {
2527 let int_steps = round_halves_positive(steps_from_base);
2530 fval = int_steps * allowed_value_step + step_base;
2532
2533 if let Some(stepped_maximum) = self.stepped_maximum() {
2537 if fval > stepped_maximum {
2538 fval = stepped_maximum;
2539 }
2540 }
2541 if let Some(stepped_minimum) = self.stepped_minimum() {
2542 if fval < stepped_minimum {
2543 fval = stepped_minimum;
2544 }
2545 }
2546 }
2547 }
2548 *value = DOMString::from(fval.to_string());
2549 };
2550 },
2551 InputType::Email => {
2552 if !self.Multiple() {
2553 value.strip_newlines();
2554 value.strip_leading_and_trailing_ascii_whitespace();
2555 } else {
2556 let sanitized = split_commas(&value.str())
2557 .map(|token| {
2558 let mut token = DOMString::from(token.to_string());
2559 token.strip_newlines();
2560 token.strip_leading_and_trailing_ascii_whitespace();
2561 token
2562 })
2563 .join(",");
2564 value.clear();
2565 value.push_str(sanitized.as_str());
2566 }
2567 },
2568 InputType::Button |
2571 InputType::Checkbox |
2572 InputType::File |
2573 InputType::Hidden |
2574 InputType::Image |
2575 InputType::Radio |
2576 InputType::Reset |
2577 InputType::Submit => (),
2578 }
2579 }
2580
2581 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
2582 fn selection(&self) -> TextControlSelection<'_, Self> {
2583 TextControlSelection::new(self, &self.textinput)
2584 }
2585
2586 #[allow(unsafe_code)]
2588 fn implicit_submission(&self, can_gc: CanGc) {
2589 let doc = self.owner_document();
2590 let node = doc.upcast::<Node>();
2591 let owner = self.form_owner();
2592 let form = match owner {
2593 None => return,
2594 Some(ref f) => f,
2595 };
2596
2597 if self.upcast::<Element>().click_in_progress() {
2598 return;
2599 }
2600 let submit_button = node
2601 .query_selector_iter(DOMString::from("input[type=submit]"))
2602 .unwrap()
2603 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2604 .find(|r| r.form_owner() == owner);
2605 match submit_button {
2606 Some(ref button) => {
2607 if button.is_instance_activatable() {
2608 button
2611 .upcast::<Node>()
2612 .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
2613 }
2614 },
2615 None => {
2616 let mut inputs = node
2617 .query_selector_iter(DOMString::from("input"))
2618 .unwrap()
2619 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2620 .filter(|input| {
2621 input.form_owner() == owner &&
2622 matches!(
2623 input.input_type(),
2624 InputType::Text |
2625 InputType::Search |
2626 InputType::Url |
2627 InputType::Tel |
2628 InputType::Email |
2629 InputType::Password |
2630 InputType::Date |
2631 InputType::Month |
2632 InputType::Week |
2633 InputType::Time |
2634 InputType::DatetimeLocal |
2635 InputType::Number
2636 )
2637 });
2638
2639 if inputs.nth(1).is_some() {
2640 return;
2642 }
2643 form.submit(
2644 SubmittedFrom::NotFromForm,
2645 FormSubmitterElement::Form(form),
2646 can_gc,
2647 );
2648 },
2649 }
2650 }
2651
2652 fn convert_string_to_number(&self, value: &str) -> Option<f64> {
2654 match self.input_type() {
2655 InputType::Date => value.parse_date_string().map(|date_time| {
2662 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2663 }),
2664 InputType::Month => value.parse_month_string().map(|date_time| {
2673 ((date_time.year() - 1970) * 12) as f64 + (date_time.month() as u8 - 1) as f64
2674 }),
2675 InputType::Week => value.parse_week_string().map(|date_time| {
2682 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2683 }),
2684 InputType::Time => value
2689 .parse_time_string()
2690 .map(|date_time| (date_time.time() - Time::MIDNIGHT).whole_milliseconds() as f64),
2691 InputType::DatetimeLocal => value.parse_local_date_time_string().map(|date_time| {
2698 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2699 }),
2700 InputType::Number | InputType::Range => parse_floating_point_number(value),
2701 _ => None,
2704 }
2705 }
2706
2707 fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
2709 match self.input_type() {
2710 InputType::Date | InputType::Week | InputType::Time | InputType::DatetimeLocal => {
2711 OffsetDateTime::from_unix_timestamp_nanos((value * 1e6) as i128)
2712 .ok()
2713 .map(|value| self.convert_datetime_to_dom_string(value))
2714 },
2715 InputType::Month => {
2716 let date = OffsetDateTime::UNIX_EPOCH;
2720 let years = (value / 12.) as i32;
2721 let year = date.year() + years;
2722
2723 let months = value as i32 - (years * 12);
2724 let months = match months.cmp(&0) {
2725 Ordering::Less => (12 - months) as u8,
2726 Ordering::Equal | Ordering::Greater => months as u8,
2727 } + 1;
2728
2729 let date = date
2730 .replace_year(year)
2731 .ok()?
2732 .replace_month(Month::try_from(months).ok()?)
2733 .ok()?;
2734 Some(self.convert_datetime_to_dom_string(date))
2735 },
2736 InputType::Number | InputType::Range => {
2737 let mut value = DOMString::from(value.to_string());
2738 value.set_best_representation_of_the_floating_point_number();
2739 Some(value)
2740 },
2741 _ => unreachable!("Should not have called convert_number_to_string for non-Date types"),
2742 }
2743 }
2744
2745 fn convert_string_to_naive_datetime(&self, value: DOMString) -> Option<OffsetDateTime> {
2749 match self.input_type() {
2750 InputType::Date => value.str().parse_date_string(),
2751 InputType::Time => value.str().parse_time_string(),
2752 InputType::Week => value.str().parse_week_string(),
2753 InputType::Month => value.str().parse_month_string(),
2754 InputType::DatetimeLocal => value.str().parse_local_date_time_string(),
2755 _ => None,
2757 }
2758 }
2759
2760 fn convert_datetime_to_dom_string(&self, value: OffsetDateTime) -> DOMString {
2764 DOMString::from_string(match self.input_type() {
2765 InputType::Date => value.to_date_string(),
2766 InputType::Month => value.to_month_string(),
2767 InputType::Week => value.to_week_string(),
2768 InputType::Time => value.to_time_string(),
2769 InputType::DatetimeLocal => value.to_local_date_time_string(),
2770 _ => {
2771 unreachable!("Should not have called convert_datetime_to_string for non-Date types")
2772 },
2773 })
2774 }
2775
2776 fn update_related_validity_states(&self, can_gc: CanGc) {
2777 match self.input_type() {
2778 InputType::Radio => {
2779 perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc)
2780 },
2781 _ => {
2782 self.validity_state(can_gc)
2783 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2784 },
2785 }
2786 }
2787
2788 fn value_changed(&self, can_gc: CanGc) {
2789 self.update_related_validity_states(can_gc);
2790 self.update_shadow_tree(can_gc);
2791 }
2792
2793 fn show_the_picker_if_applicable(&self) {
2795 if !self.is_mutable() {
2799 return;
2800 }
2801
2802 if self.input_type() == InputType::Color {
2805 let document = self.owner_document();
2806 let current_value = self.Value();
2807 let current_color = RgbColor {
2808 red: u8::from_str_radix(¤t_value.str()[1..3], 16).unwrap(),
2809 green: u8::from_str_radix(¤t_value.str()[3..5], 16).unwrap(),
2810 blue: u8::from_str_radix(¤t_value.str()[5..7], 16).unwrap(),
2811 };
2812 document.embedder_controls().show_embedder_control(
2813 ControlElement::ColorInput(DomRoot::from_ref(self)),
2814 EmbedderControlRequest::ColorPicker(current_color),
2815 );
2816 }
2817 }
2818
2819 pub(crate) fn handle_color_picker_response(&self, response: Option<RgbColor>, can_gc: CanGc) {
2820 let Some(selected_color) = response else {
2821 return;
2822 };
2823 let formatted_color = format!(
2824 "#{:0>2x}{:0>2x}{:0>2x}",
2825 selected_color.red, selected_color.green, selected_color.blue
2826 );
2827 let _ = self.SetValue(formatted_color.into(), can_gc);
2828 }
2829
2830 pub(crate) fn handle_file_picker_response(
2831 &self,
2832 response: Option<Vec<SelectedFile>>,
2833 can_gc: CanGc,
2834 ) {
2835 let mut files = Vec::new();
2836
2837 if let Some(pending_webdriver_reponse) = self.pending_webdriver_response.borrow_mut().take()
2838 {
2839 if self.Multiple() {
2846 if let Some(filelist) = self.filelist.get() {
2847 files = filelist.iter_files().map(|file| file.as_rooted()).collect();
2848 }
2849 }
2850
2851 let number_files_selected = response.as_ref().map(Vec::len).unwrap_or_default();
2852 pending_webdriver_reponse.finish(number_files_selected);
2853 }
2854
2855 let Some(response_files) = response else {
2856 return;
2857 };
2858
2859 let window = self.owner_window();
2860 files.extend(
2861 response_files
2862 .into_iter()
2863 .map(|file| File::new_from_selected(&window, file, can_gc)),
2864 );
2865
2866 if !self.Multiple() {
2869 files = files
2870 .pop()
2871 .map(|last_file| vec![last_file])
2872 .unwrap_or_default();
2873 }
2874
2875 self.filelist
2876 .set(Some(&FileList::new(&window, files, can_gc)));
2877
2878 let target = self.upcast::<EventTarget>();
2879 target.fire_bubbling_event(atom!("input"), can_gc);
2880 target.fire_bubbling_event(atom!("change"), can_gc);
2881 }
2882
2883 fn handle_focus(&self) {
2884 let Ok(input_method_type) = self.input_type().try_into() else {
2885 return;
2886 };
2887
2888 self.owner_document()
2889 .embedder_controls()
2890 .show_embedder_control(
2891 ControlElement::Ime(DomRoot::from_ref(self.upcast())),
2892 EmbedderControlRequest::InputMethod(InputMethodRequest {
2893 input_method_type,
2894 text: self.Value().to_string(),
2895 insertion_point: self.GetSelectionEnd(),
2896 multiline: false,
2897 }),
2898 );
2899 }
2900}
2901
2902impl VirtualMethods for HTMLInputElement {
2903 fn super_type(&self) -> Option<&dyn VirtualMethods> {
2904 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
2905 }
2906
2907 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
2908 let could_have_had_embedder_control = self.may_have_embedder_control();
2909
2910 self.super_type()
2911 .unwrap()
2912 .attribute_mutated(attr, mutation, can_gc);
2913
2914 match *attr.local_name() {
2915 local_name!("disabled") => {
2916 let disabled_state = match mutation {
2917 AttributeMutation::Set(None) => true,
2918 AttributeMutation::Set(Some(_)) => {
2919 return;
2921 },
2922 AttributeMutation::Removed => false,
2923 };
2924 let el = self.upcast::<Element>();
2925 el.set_disabled_state(disabled_state);
2926 el.set_enabled_state(!disabled_state);
2927 el.check_ancestors_disabled_state_for_form_control();
2928
2929 if self.input_type().is_textual() {
2930 let read_write = !(self.ReadOnly() || el.disabled_state());
2931 el.set_read_write_state(read_write);
2932 }
2933
2934 el.update_sequentially_focusable_status(can_gc);
2935 },
2936 local_name!("checked") if !self.checked_changed.get() => {
2937 let checked_state = match mutation {
2938 AttributeMutation::Set(None) => true,
2939 AttributeMutation::Set(Some(_)) => {
2940 return;
2942 },
2943 AttributeMutation::Removed => false,
2944 };
2945 self.update_checked_state(checked_state, false, can_gc);
2946 },
2947 local_name!("size") => {
2948 let size = mutation.new_value(attr).map(|value| value.as_uint());
2949 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
2950 },
2951 local_name!("type") => {
2952 let el = self.upcast::<Element>();
2953 match mutation {
2954 AttributeMutation::Set(_) => {
2955 let new_type = InputType::from(attr.value().as_atom());
2956
2957 let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
2959 let previously_selectable = self.selection_api_applies();
2960
2961 self.input_type.set(new_type);
2962
2963 if new_type.is_textual() {
2964 let read_write = !(self.ReadOnly() || el.disabled_state());
2965 el.set_read_write_state(read_write);
2966 } else {
2967 el.set_read_write_state(false);
2968 }
2969
2970 if new_type == InputType::File {
2971 let window = self.owner_window();
2972 let filelist = FileList::new(&window, vec![], can_gc);
2973 self.filelist.set(Some(&filelist));
2974 }
2975
2976 let new_value_mode = self.value_mode();
2977 match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
2978 (&ValueMode::Value, false, ValueMode::Default) |
2980 (&ValueMode::Value, false, ValueMode::DefaultOn) => {
2981 self.SetValue(old_idl_value, can_gc)
2982 .expect("Failed to set input value on type change to a default ValueMode.");
2983 },
2984
2985 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
2987 self.SetValue(
2988 self.upcast::<Element>()
2989 .get_attribute(&ns!(), &local_name!("value"))
2990 .map_or(DOMString::from(""), |a| {
2991 DOMString::from(a.summarize().value)
2992 }),
2993 can_gc,
2994 )
2995 .expect(
2996 "Failed to set input value on type change to ValueMode::Value.",
2997 );
2998 self.value_dirty.set(false);
2999 },
3000
3001 (_, _, ValueMode::Filename)
3003 if old_value_mode != ValueMode::Filename =>
3004 {
3005 self.SetValue(DOMString::from(""), can_gc)
3006 .expect("Failed to set input value on type change to ValueMode::Filename.");
3007 },
3008 _ => {},
3009 }
3010
3011 if new_type == InputType::Radio {
3013 self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3014 }
3015
3016 let mut textinput = self.textinput.borrow_mut();
3018 let mut value = textinput.single_line_content().clone();
3019 self.sanitize_value(&mut value);
3020 textinput.set_content(value);
3021 self.upcast::<Node>().dirty(NodeDamage::Other);
3022
3023 if !previously_selectable && self.selection_api_applies() {
3025 textinput.clear_selection_to_limit(Direction::Backward);
3026 }
3027 },
3028 AttributeMutation::Removed => {
3029 if self.input_type() == InputType::Radio {
3030 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
3031 }
3032 self.input_type.set(InputType::default());
3033 let el = self.upcast::<Element>();
3034
3035 let read_write = !(self.ReadOnly() || el.disabled_state());
3036 el.set_read_write_state(read_write);
3037 },
3038 }
3039
3040 self.update_placeholder_shown_state();
3041 self.update_text_shadow_tree_placeholder(can_gc);
3042 },
3043 local_name!("value") if !self.value_dirty.get() => {
3046 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
3047 let mut value = value.map_or(DOMString::new(), DOMString::from);
3048
3049 self.sanitize_value(&mut value);
3050 self.textinput.borrow_mut().set_content(value);
3051 self.update_placeholder_shown_state();
3052
3053 self.upcast::<Node>().dirty(NodeDamage::Other);
3054 },
3055 local_name!("name") if self.input_type() == InputType::Radio => {
3056 self.radio_group_updated(
3057 mutation.new_value(attr).as_ref().map(|name| name.as_atom()),
3058 can_gc,
3059 );
3060 },
3061 local_name!("maxlength") => match *attr.value() {
3062 AttrValue::Int(_, value) => {
3063 let mut textinput = self.textinput.borrow_mut();
3064
3065 if value < 0 {
3066 textinput.set_max_length(None);
3067 } else {
3068 textinput.set_max_length(Some(UTF16CodeUnits(value as usize)))
3069 }
3070 },
3071 _ => panic!("Expected an AttrValue::Int"),
3072 },
3073 local_name!("minlength") => match *attr.value() {
3074 AttrValue::Int(_, value) => {
3075 let mut textinput = self.textinput.borrow_mut();
3076
3077 if value < 0 {
3078 textinput.set_min_length(None);
3079 } else {
3080 textinput.set_min_length(Some(UTF16CodeUnits(value as usize)))
3081 }
3082 },
3083 _ => panic!("Expected an AttrValue::Int"),
3084 },
3085 local_name!("placeholder") => {
3086 {
3087 let mut placeholder = self.placeholder.borrow_mut();
3088 placeholder.clear();
3089 if let AttributeMutation::Set(_) = mutation {
3090 placeholder
3091 .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
3092 }
3093 }
3094 self.update_placeholder_shown_state();
3095 self.update_text_shadow_tree_placeholder(can_gc);
3096 },
3097 local_name!("readonly") => {
3098 if self.input_type().is_textual() {
3099 let el = self.upcast::<Element>();
3100 match mutation {
3101 AttributeMutation::Set(_) => {
3102 el.set_read_write_state(false);
3103 },
3104 AttributeMutation::Removed => {
3105 el.set_read_write_state(!el.disabled_state());
3106 },
3107 }
3108 }
3109 },
3110 local_name!("form") => {
3111 self.form_attribute_mutated(mutation, can_gc);
3112 },
3113 _ => {},
3114 }
3115
3116 self.value_changed(can_gc);
3117
3118 if could_have_had_embedder_control && !self.may_have_embedder_control() {
3119 self.owner_document()
3120 .embedder_controls()
3121 .hide_embedder_control(self.upcast());
3122 }
3123 }
3124
3125 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
3126 match *name {
3127 local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
3128 local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
3129 local_name!("type") => AttrValue::from_atomic(value.into()),
3130 local_name!("maxlength") => {
3131 AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
3132 },
3133 local_name!("minlength") => {
3134 AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
3135 },
3136 _ => self
3137 .super_type()
3138 .unwrap()
3139 .parse_plain_attribute(name, value),
3140 }
3141 }
3142
3143 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
3144 if let Some(s) = self.super_type() {
3145 s.bind_to_tree(context, can_gc);
3146 }
3147 self.upcast::<Element>()
3148 .check_ancestors_disabled_state_for_form_control();
3149
3150 if self.input_type() == InputType::Radio {
3151 self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3152 }
3153
3154 self.value_changed(can_gc);
3155 }
3156
3157 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
3158 let form_owner = self.form_owner();
3159
3160 self.super_type().unwrap().unbind_from_tree(context, can_gc);
3161
3162 let node = self.upcast::<Node>();
3163 let el = self.upcast::<Element>();
3164 if node
3165 .ancestors()
3166 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
3167 {
3168 el.check_ancestors_disabled_state_for_form_control();
3169 } else {
3170 el.check_disabled_attribute();
3171 }
3172
3173 if self.input_type() == InputType::Radio {
3174 let root = context.parent.GetRootNode(&GetRootNodeOptions::empty());
3175 for r in radio_group_iter(
3176 self,
3177 self.radio_group_name().as_ref(),
3178 form_owner.as_deref(),
3179 &root,
3180 ) {
3181 r.validity_state(can_gc)
3182 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3183 }
3184 }
3185
3186 self.validity_state(can_gc)
3187 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3188
3189 if self.input_type() == InputType::Color {
3190 self.owner_document()
3191 .embedder_controls()
3192 .hide_embedder_control(self.upcast());
3193 }
3194 }
3195
3196 fn handle_event(&self, event: &Event, can_gc: CanGc) {
3202 if let Some(s) = self.super_type() {
3203 s.handle_event(event, can_gc);
3204 }
3205
3206 if event.type_() == atom!("click") && !event.DefaultPrevented() {
3207 if self.input_type().is_textual_or_password() &&
3213 !self.textinput.borrow().is_empty()
3215 {
3216 if let Some(mouse_event) = event.downcast::<MouseEvent>() {
3217 if let Some(point_in_target) = mouse_event.point_in_target() {
3221 let window = self.owner_window();
3222 let index = window
3223 .text_index_query(self.upcast::<Node>(), point_in_target.to_untyped());
3224 let edit_point_index = match index {
3227 Some(i) => i,
3228 None => self.textinput.borrow().char_count(),
3229 };
3230 self.textinput
3231 .borrow_mut()
3232 .set_edit_point_index(edit_point_index);
3233 self.upcast::<Node>().dirty(NodeDamage::Other);
3235 event.PreventDefault();
3236 }
3237 }
3238 }
3239 } else if event.type_() == atom!("keydown") &&
3240 !event.DefaultPrevented() &&
3241 self.input_type().is_textual_or_password()
3242 {
3243 if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
3244 let action = self.textinput.borrow_mut().handle_keydown(keyevent);
3247 match action {
3248 TriggerDefaultAction => {
3249 self.implicit_submission(can_gc);
3250 },
3251 DispatchInput => {
3252 if event.IsTrusted() {
3253 self.owner_global()
3254 .task_manager()
3255 .user_interaction_task_source()
3256 .queue_event(
3257 self.upcast(),
3258 atom!("input"),
3259 EventBubbles::Bubbles,
3260 EventCancelable::NotCancelable,
3261 );
3262 }
3263 self.value_dirty.set(true);
3264 self.update_placeholder_shown_state();
3265 self.upcast::<Node>().dirty(NodeDamage::Other);
3266 event.mark_as_handled();
3267 },
3268 RedrawSelection => {
3269 self.upcast::<Node>().dirty(NodeDamage::Other);
3270 event.mark_as_handled();
3271 },
3272 Nothing => (),
3273 }
3274 }
3275 } else if event.type_() == atom!("keypress") &&
3276 !event.DefaultPrevented() &&
3277 self.input_type().is_textual_or_password()
3278 {
3279 } else if (event.type_() == atom!("compositionstart") ||
3283 event.type_() == atom!("compositionupdate") ||
3284 event.type_() == atom!("compositionend")) &&
3285 self.input_type().is_textual_or_password()
3286 {
3287 if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
3288 if event.type_() == atom!("compositionend") {
3289 let _ = self
3290 .textinput
3291 .borrow_mut()
3292 .handle_compositionend(compositionevent);
3293 self.upcast::<Node>().dirty(NodeDamage::Other);
3294 self.update_placeholder_shown_state();
3295 } else if event.type_() == atom!("compositionupdate") {
3296 let _ = self
3297 .textinput
3298 .borrow_mut()
3299 .handle_compositionupdate(compositionevent);
3300 self.upcast::<Node>().dirty(NodeDamage::Other);
3301 self.update_placeholder_shown_state();
3302 } else if event.type_() == atom!("compositionstart") {
3303 self.update_placeholder_shown_state();
3305 }
3306 event.mark_as_handled();
3307 }
3308 } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
3309 let reaction = self
3310 .textinput
3311 .borrow_mut()
3312 .handle_clipboard_event(clipboard_event);
3313 if reaction.contains(ClipboardEventReaction::FireClipboardChangedEvent) {
3314 self.owner_document()
3315 .event_handler()
3316 .fire_clipboardchange_event(can_gc);
3317 }
3318 if reaction.contains(ClipboardEventReaction::QueueInputEvent) {
3319 self.owner_global()
3320 .task_manager()
3321 .user_interaction_task_source()
3322 .queue_event(
3323 self.upcast(),
3324 atom!("input"),
3325 EventBubbles::Bubbles,
3326 EventCancelable::NotCancelable,
3327 );
3328 }
3329 if !reaction.is_empty() {
3330 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
3331 }
3332 } else if let Some(event) = event.downcast::<FocusEvent>() {
3333 if *event.upcast::<Event>().type_() == *"blur" {
3334 self.owner_document()
3335 .embedder_controls()
3336 .hide_embedder_control(self.upcast());
3337 }
3338 if *event.upcast::<Event>().type_() == *"focus" {
3339 self.handle_focus();
3340 }
3341 }
3342
3343 self.value_changed(can_gc);
3344 }
3345
3346 fn cloning_steps(
3348 &self,
3349 copy: &Node,
3350 maybe_doc: Option<&Document>,
3351 clone_children: CloneChildrenFlag,
3352 can_gc: CanGc,
3353 ) {
3354 if let Some(s) = self.super_type() {
3355 s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
3356 }
3357 let elem = copy.downcast::<HTMLInputElement>().unwrap();
3358 elem.value_dirty.set(self.value_dirty.get());
3359 elem.checked_changed.set(self.checked_changed.get());
3360 elem.upcast::<Element>()
3361 .set_state(ElementState::CHECKED, self.Checked());
3362 elem.textinput
3363 .borrow_mut()
3364 .set_content(self.textinput.borrow().get_content());
3365 self.value_changed(can_gc);
3366 }
3367}
3368
3369impl FormControl for HTMLInputElement {
3370 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
3371 self.form_owner.get()
3372 }
3373
3374 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
3375 self.form_owner.set(form);
3376 }
3377
3378 fn to_element(&self) -> &Element {
3379 self.upcast::<Element>()
3380 }
3381}
3382
3383impl Validatable for HTMLInputElement {
3384 fn as_element(&self) -> &Element {
3385 self.upcast()
3386 }
3387
3388 fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
3389 self.validity_state
3390 .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
3391 }
3392
3393 fn is_instance_validatable(&self) -> bool {
3394 match self.input_type() {
3401 InputType::Hidden | InputType::Button | InputType::Reset => false,
3402 _ => {
3403 !(self.upcast::<Element>().disabled_state() ||
3404 self.ReadOnly() ||
3405 is_barred_by_datalist_ancestor(self.upcast()))
3406 },
3407 }
3408 }
3409
3410 fn perform_validation(
3411 &self,
3412 validate_flags: ValidationFlags,
3413 can_gc: CanGc,
3414 ) -> ValidationFlags {
3415 let mut failed_flags = ValidationFlags::empty();
3416 let value = self.Value();
3417
3418 if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
3419 self.suffers_from_being_missing(&value)
3420 {
3421 failed_flags.insert(ValidationFlags::VALUE_MISSING);
3422 }
3423
3424 if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
3425 self.suffers_from_type_mismatch(&value)
3426 {
3427 failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
3428 }
3429
3430 if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
3431 self.suffers_from_pattern_mismatch(&value, can_gc)
3432 {
3433 failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
3434 }
3435
3436 if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
3437 self.suffers_from_bad_input(&value)
3438 {
3439 failed_flags.insert(ValidationFlags::BAD_INPUT);
3440 }
3441
3442 if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
3443 failed_flags |= self.suffers_from_length_issues(&value);
3444 }
3445
3446 if validate_flags.intersects(
3447 ValidationFlags::RANGE_UNDERFLOW |
3448 ValidationFlags::RANGE_OVERFLOW |
3449 ValidationFlags::STEP_MISMATCH,
3450 ) {
3451 failed_flags |= self.suffers_from_range_issues(&value);
3452 }
3453
3454 failed_flags & validate_flags
3455 }
3456}
3457
3458impl Activatable for HTMLInputElement {
3459 fn as_element(&self) -> &Element {
3460 self.upcast()
3461 }
3462
3463 fn is_instance_activatable(&self) -> bool {
3464 match self.input_type() {
3465 InputType::Submit | InputType::Reset | InputType::File | InputType::Image => {
3470 self.is_mutable()
3471 },
3472 InputType::Checkbox | InputType::Radio | InputType::Color => true,
3476 _ => false,
3477 }
3478 }
3479
3480 fn legacy_pre_activation_behavior(&self, can_gc: CanGc) -> Option<InputActivationState> {
3482 let ty = self.input_type();
3483 let activation_state = match ty {
3484 InputType::Checkbox => {
3485 let was_checked = self.Checked();
3486 let was_indeterminate = self.Indeterminate();
3487 self.SetIndeterminate(false);
3488 self.SetChecked(!was_checked, can_gc);
3489 Some(InputActivationState {
3490 checked: was_checked,
3491 indeterminate: was_indeterminate,
3492 checked_radio: None,
3493 old_type: InputType::Checkbox,
3494 })
3495 },
3496 InputType::Radio => {
3497 let root = self
3498 .upcast::<Node>()
3499 .GetRootNode(&GetRootNodeOptions::empty());
3500 let form_owner = self.form_owner();
3501 let checked_member = radio_group_iter(
3502 self,
3503 self.radio_group_name().as_ref(),
3504 form_owner.as_deref(),
3505 &root,
3506 )
3507 .find(|r| r.Checked());
3508 let was_checked = self.Checked();
3509 self.SetChecked(true, can_gc);
3510 Some(InputActivationState {
3511 checked: was_checked,
3512 indeterminate: false,
3513 checked_radio: checked_member.as_deref().map(DomRoot::from_ref),
3514 old_type: InputType::Radio,
3515 })
3516 },
3517 _ => None,
3518 };
3519
3520 if activation_state.is_some() {
3521 self.value_changed(can_gc);
3522 }
3523
3524 activation_state
3525 }
3526
3527 fn legacy_canceled_activation_behavior(
3529 &self,
3530 cache: Option<InputActivationState>,
3531 can_gc: CanGc,
3532 ) {
3533 let ty = self.input_type();
3535 let cache = match cache {
3536 Some(cache) => {
3537 if cache.old_type != ty {
3538 return;
3541 }
3542 cache
3543 },
3544 None => {
3545 return;
3546 },
3547 };
3548
3549 match ty {
3550 InputType::Checkbox => {
3552 self.SetIndeterminate(cache.indeterminate);
3553 self.SetChecked(cache.checked, can_gc);
3554 },
3555 InputType::Radio => {
3557 if let Some(ref o) = cache.checked_radio {
3558 let tree_root = self
3559 .upcast::<Node>()
3560 .GetRootNode(&GetRootNodeOptions::empty());
3561 if in_same_group(
3564 o,
3565 self.form_owner().as_deref(),
3566 self.radio_group_name().as_ref(),
3567 Some(&*tree_root),
3568 ) {
3569 o.SetChecked(true, can_gc);
3570 } else {
3571 self.SetChecked(false, can_gc);
3572 }
3573 } else {
3574 self.SetChecked(false, can_gc);
3575 }
3576 },
3577 _ => (),
3578 }
3579
3580 self.value_changed(can_gc);
3581 }
3582
3583 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) {
3585 match self.input_type() {
3586 InputType::Submit | InputType::Image => {
3589 if let Some(form_owner) = self.form_owner() {
3591 let document = self.owner_document();
3593
3594 if !document.is_fully_active() {
3595 return;
3596 }
3597
3598 form_owner.submit(
3601 SubmittedFrom::NotFromForm,
3602 FormSubmitterElement::Input(self),
3603 can_gc,
3604 )
3605 }
3606 },
3607 InputType::Reset => {
3608 if let Some(form_owner) = self.form_owner() {
3611 let document = self.owner_document();
3612
3613 if !document.is_fully_active() {
3615 return;
3616 }
3617
3618 form_owner.reset(ResetFrom::NotFromForm, can_gc);
3620 }
3621 },
3622 InputType::Checkbox | InputType::Radio => {
3625 if !self.upcast::<Node>().is_connected() {
3627 return;
3628 }
3629
3630 let target = self.upcast::<EventTarget>();
3631
3632 target.fire_bubbling_event(atom!("input"), can_gc);
3635
3636 target.fire_bubbling_event(atom!("change"), can_gc);
3639 },
3640 InputType::File => {
3642 self.select_files(None);
3643 },
3644 InputType::Color => {
3646 self.show_the_picker_if_applicable();
3647 },
3648 _ => (),
3649 }
3650 }
3651}
3652
3653fn filter_from_accept(s: &DOMString) -> Vec<FilterPattern> {
3655 let mut filter = vec![];
3656 for p in split_commas(&s.str()) {
3657 let p = p.trim();
3658 if let Some('.') = p.chars().next() {
3659 filter.push(FilterPattern(p[1..].to_string()));
3660 } else if let Some(exts) = mime_guess::get_mime_extensions_str(p) {
3661 for ext in exts {
3662 filter.push(FilterPattern(ext.to_string()));
3663 }
3664 }
3665 }
3666
3667 filter
3668}
3669
3670fn round_halves_positive(n: f64) -> f64 {
3671 if n.fract() == -0.5 {
3675 n.ceil()
3676 } else {
3677 n.round()
3678 }
3679}
3680
3681fn compile_pattern(
3685 cx: SafeJSContext,
3686 pattern_str: &str,
3687 out_regex: MutableHandleObject,
3688 can_gc: CanGc,
3689) -> bool {
3690 if check_js_regex_syntax(cx, pattern_str, can_gc) {
3692 let pattern_str = format!("^(?:{})$", pattern_str);
3694 let flags = RegExpFlags {
3695 flags_: RegExpFlag_UnicodeSets,
3696 };
3697 new_js_regex(cx, &pattern_str, flags, out_regex, can_gc)
3698 } else {
3699 false
3700 }
3701}
3702
3703#[allow(unsafe_code)]
3704fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str, _can_gc: CanGc) -> bool {
3707 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3708 unsafe {
3709 rooted!(in(*cx) let mut exception = UndefinedValue());
3710
3711 let valid = CheckRegExpSyntax(
3712 *cx,
3713 pattern.as_ptr(),
3714 pattern.len(),
3715 RegExpFlags {
3716 flags_: RegExpFlag_UnicodeSets,
3717 },
3718 exception.handle_mut(),
3719 );
3720
3721 if !valid {
3722 JS_ClearPendingException(*cx);
3723 return false;
3724 }
3725
3726 exception.is_undefined()
3729 }
3730}
3731
3732#[allow(unsafe_code)]
3733pub(crate) fn new_js_regex(
3734 cx: SafeJSContext,
3735 pattern: &str,
3736 flags: RegExpFlags,
3737 mut out_regex: MutableHandleObject,
3738 _can_gc: CanGc,
3739) -> bool {
3740 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3741 unsafe {
3742 out_regex.set(NewUCRegExpObject(
3743 *cx,
3744 pattern.as_ptr(),
3745 pattern.len(),
3746 flags,
3747 ));
3748 if out_regex.is_null() {
3749 JS_ClearPendingException(*cx);
3750 return false;
3751 }
3752 }
3753 true
3754}
3755
3756#[allow(unsafe_code)]
3757fn matches_js_regex(
3758 cx: SafeJSContext,
3759 regex_obj: HandleObject,
3760 value: &str,
3761 _can_gc: CanGc,
3762) -> Result<bool, ()> {
3763 let mut value: Vec<u16> = value.encode_utf16().collect();
3764
3765 unsafe {
3766 let mut is_regex = false;
3767 assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
3768 assert!(is_regex);
3769
3770 rooted!(in(*cx) let mut rval = UndefinedValue());
3771 let mut index = 0;
3772
3773 let ok = ExecuteRegExpNoStatics(
3774 *cx,
3775 regex_obj,
3776 value.as_mut_ptr(),
3777 value.len(),
3778 &mut index,
3779 true,
3780 rval.handle_mut(),
3781 );
3782
3783 if ok {
3784 Ok(!rval.is_null())
3785 } else {
3786 JS_ClearPendingException(*cx);
3787 Err(())
3788 }
3789 }
3790}
3791
3792#[derive(MallocSizeOf)]
3796struct PendingWebDriverResponse {
3797 response_sender: IpcSender<Result<bool, ErrorStatus>>,
3799 expected_file_count: usize,
3801}
3802
3803impl PendingWebDriverResponse {
3804 fn finish(self, number_files_selected: usize) {
3805 if number_files_selected == self.expected_file_count {
3806 let _ = self.response_sender.send(Ok(false));
3807 } else {
3808 let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
3811 }
3812 }
3813}