1use std::cell::{Cell, RefCell};
6use std::cmp::Ordering;
7use std::path::PathBuf;
8use std::ptr::NonNull;
9use std::str::FromStr;
10use std::{f64, ptr};
11
12use base::generic_channel::GenericSender;
13use base::text::Utf16CodeUnitLength;
14use cssparser::{Parser, ParserInput};
15use dom_struct::dom_struct;
16use embedder_traits::{
17 EmbedderControlRequest, FilePickerRequest, FilterPattern, InputMethodRequest, InputMethodType,
18 RgbColor, SelectedFile,
19};
20use encoding_rs::Encoding;
21use fonts::{ByteIndex, TextByteRange};
22use html5ever::{LocalName, Prefix, QualName, local_name, ns};
23use itertools::Itertools;
24use js::jsapi::{
25 ClippedTime, DateGetMsecSinceEpoch, Handle, JS_ClearPendingException, JSObject, NewDateObject,
26 NewUCRegExpObject, ObjectIsDate, RegExpFlag_UnicodeSets, RegExpFlags,
27};
28use js::jsval::UndefinedValue;
29use js::rust::wrappers::{CheckRegExpSyntax, ExecuteRegExpNoStatics, ObjectIsRegExp};
30use js::rust::{HandleObject, MutableHandleObject};
31use layout_api::wrapper_traits::{ScriptSelection, SharedSelection};
32use script_bindings::codegen::GenericBindings::AttrBinding::AttrMethods;
33use script_bindings::codegen::GenericBindings::CharacterDataBinding::CharacterDataMethods;
34use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
35use script_bindings::domstring::parse_floating_point_number;
36use style::attr::AttrValue;
37use style::color::{AbsoluteColor, ColorFlags, ColorSpace};
38use style::context::QuirksMode;
39use style::parser::ParserContext;
40use style::selector_parser::PseudoElement;
41use style::str::split_commas;
42use style::stylesheets::CssRuleType;
43use style::stylesheets::origin::Origin;
44use style::values::specified::color::Color;
45use style_traits::{ParsingMode, ToCss};
46use stylo_atoms::Atom;
47use stylo_dom::ElementState;
48use time::{Month, OffsetDateTime, Time};
49use unicode_bidi::{BidiClass, bidi_class};
50use url::Url;
51use webdriver::error::ErrorStatus;
52
53use crate::clipboard_provider::EmbedderClipboardProvider;
54use crate::dom::activation::Activatable;
55use crate::dom::attr::Attr;
56use crate::dom::bindings::cell::{DomRefCell, Ref};
57use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
58use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
59use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
60use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
61use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
62use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
63use crate::dom::bindings::error::{Error, ErrorResult};
64use crate::dom::bindings::inheritance::Castable;
65use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
66use crate::dom::bindings::str::{DOMString, FromInputValueString, ToInputValueString, USVString};
67use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType};
68use crate::dom::compositionevent::CompositionEvent;
69use crate::dom::document::Document;
70use crate::dom::document_embedder_controls::ControlElement;
71use crate::dom::element::{AttributeMutation, CustomElementCreationMode, Element, ElementCreator};
72use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
73use crate::dom::eventtarget::EventTarget;
74use crate::dom::file::File;
75use crate::dom::filelist::FileList;
76use crate::dom::globalscope::GlobalScope;
77use crate::dom::html::htmldatalistelement::HTMLDataListElement;
78use crate::dom::html::htmlelement::HTMLElement;
79use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
80use crate::dom::html::htmlformelement::{
81 FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom,
82 SubmittedFrom,
83};
84use crate::dom::keyboardevent::KeyboardEvent;
85use crate::dom::node::{
86 BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext,
87};
88use crate::dom::nodelist::NodeList;
89use crate::dom::text::Text;
90use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
91use crate::dom::types::{CharacterData, FocusEvent, MouseEvent};
92use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
93use crate::dom::validitystate::{ValidationFlags, ValidityState};
94use crate::dom::virtualmethods::VirtualMethods;
95use crate::realms::enter_realm;
96use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
97use crate::textinput::{ClipboardEventFlags, IsComposing, KeyReaction, Lines, TextInput};
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 TextValueShadowTree {
107 value: Dom<Text>,
108}
109
110impl TextValueShadowTree {
111 fn new(shadow_root: &Node, can_gc: CanGc) -> Self {
112 let value = Text::new(Default::default(), &shadow_root.owner_document(), can_gc);
113 Node::replace_all(Some(value.upcast()), shadow_root, can_gc);
114 Self {
115 value: value.as_traced(),
116 }
117 }
118
119 fn update(&self, input_element: &HTMLInputElement) {
120 let character_data = self.value.upcast::<CharacterData>();
121 let value = input_element.value_for_shadow_dom();
122 if character_data.Data() != value {
123 character_data.SetData(value);
124 }
125 }
126}
127
128#[derive(Clone, JSTraceable, MallocSizeOf)]
129#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
130struct TextInputWidgetShadowTree {
150 inner_container: Dom<Element>,
151 text_container: Dom<Element>,
152 placeholder_container: DomRefCell<Option<Dom<Element>>>,
153}
154
155impl TextInputWidgetShadowTree {
156 fn new(shadow_root: &Node, can_gc: CanGc) -> Self {
157 let document = shadow_root.owner_document();
158 let inner_container = Element::create(
159 QualName::new(None, ns!(html), local_name!("div")),
160 None,
161 &document,
162 ElementCreator::ScriptCreated,
163 CustomElementCreationMode::Asynchronous,
164 None,
165 can_gc,
166 );
167
168 Node::replace_all(Some(inner_container.upcast()), shadow_root.upcast(), can_gc);
169 inner_container
170 .upcast::<Node>()
171 .set_implemented_pseudo_element(PseudoElement::ServoTextControlInnerContainer);
172
173 let text_container = create_ua_widget_div_with_text_node(
174 &document,
175 inner_container.upcast(),
176 PseudoElement::ServoTextControlInnerEditor,
177 false,
178 can_gc,
179 );
180
181 Self {
182 inner_container: inner_container.as_traced(),
183 text_container: text_container.as_traced(),
184 placeholder_container: DomRefCell::new(None),
185 }
186 }
187
188 fn init_placeholder_container_if_necessary(
191 &self,
192 host: &HTMLInputElement,
193 can_gc: CanGc,
194 ) -> Option<DomRoot<Element>> {
195 if let Some(placeholder_container) = &*self.placeholder_container.borrow() {
196 return Some(placeholder_container.root_element());
197 }
198 if host.placeholder.borrow().is_empty() {
201 return None;
202 }
203
204 let placeholder_container = create_ua_widget_div_with_text_node(
205 &host.owner_document(),
206 self.inner_container.upcast::<Node>(),
207 PseudoElement::Placeholder,
208 true,
209 can_gc,
210 );
211 *self.placeholder_container.borrow_mut() = Some(placeholder_container.as_traced());
212 Some(placeholder_container)
213 }
214
215 fn placeholder_character_data(
216 &self,
217 input_element: &HTMLInputElement,
218 can_gc: CanGc,
219 ) -> Option<DomRoot<CharacterData>> {
220 self.init_placeholder_container_if_necessary(input_element, can_gc)
221 .and_then(|placeholder_container| {
222 let first_child = placeholder_container.upcast::<Node>().GetFirstChild()?;
223 Some(DomRoot::from_ref(first_child.downcast::<CharacterData>()?))
224 })
225 }
226
227 fn update_placeholder(&self, input_element: &HTMLInputElement, can_gc: CanGc) {
228 if let Some(character_data) = self.placeholder_character_data(input_element, can_gc) {
229 let placeholder_value = input_element.placeholder.borrow().clone();
230 if character_data.Data() != placeholder_value {
231 character_data.SetData(placeholder_value.clone());
232 }
233 }
234 }
235
236 fn value_character_data(&self) -> Option<DomRoot<CharacterData>> {
237 Some(DomRoot::from_ref(
238 self.text_container
239 .upcast::<Node>()
240 .GetFirstChild()?
241 .downcast::<CharacterData>()?,
242 ))
243 }
244
245 fn update(&self, input_element: &HTMLInputElement) {
248 let value = input_element.Value();
257 let value_text = match (value.is_empty(), input_element.input_type()) {
258 (false, InputType::Password) => value
260 .str()
261 .chars()
262 .map(|_| PASSWORD_REPLACEMENT_CHAR)
263 .collect::<String>()
264 .into(),
265 (false, _) => value,
266 (true, _) => "\u{200B}".into(),
267 };
268
269 if let Some(character_data) = self.value_character_data() {
270 if character_data.Data() != value_text {
271 character_data.SetData(value_text);
272 }
273 }
274 }
275}
276
277#[derive(Clone, JSTraceable, MallocSizeOf)]
278#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
279struct ColorInputShadowTree {
284 color_value: Dom<Element>,
285}
286
287impl ColorInputShadowTree {
288 fn new(shadow_root: &Node, can_gc: CanGc) -> Self {
289 let color_value = Element::create(
290 QualName::new(None, ns!(html), local_name!("div")),
291 None,
292 &shadow_root.owner_document(),
293 ElementCreator::ScriptCreated,
294 CustomElementCreationMode::Asynchronous,
295 None,
296 can_gc,
297 );
298
299 Node::replace_all(Some(color_value.upcast()), shadow_root.upcast(), can_gc);
300 color_value
301 .upcast::<Node>()
302 .set_implemented_pseudo_element(PseudoElement::ColorSwatch);
303
304 Self {
305 color_value: color_value.as_traced(),
306 }
307 }
308
309 fn update(&self, input_element: &HTMLInputElement, can_gc: CanGc) {
310 let value = input_element.Value();
311 let style = format!("background-color: {value}");
312 self.color_value
313 .set_string_attribute(&local_name!("style"), style.into(), can_gc);
314 }
315}
316
317#[derive(Clone, JSTraceable, MallocSizeOf)]
318#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
319#[non_exhaustive]
320enum InputElementShadowTree {
321 ColorInput(ColorInputShadowTree),
322 TextInput(TextInputWidgetShadowTree),
323 TextValue(TextValueShadowTree),
324 }
326
327impl InputElementShadowTree {
328 fn new(input_element: &HTMLInputElement, can_gc: CanGc) -> Self {
329 let element = input_element.upcast::<Element>();
330 let shadow_root = element
331 .shadow_root()
332 .unwrap_or_else(|| element.attach_ua_shadow_root(true, can_gc));
333 let shadow_root = shadow_root.upcast();
334
335 if input_element.input_type() == InputType::Color {
336 return Self::ColorInput(ColorInputShadowTree::new(shadow_root, can_gc));
337 }
338 if input_element.renders_as_text_input_widget() {
339 return Self::TextInput(TextInputWidgetShadowTree::new(shadow_root, can_gc));
340 }
341 Self::TextValue(TextValueShadowTree::new(shadow_root, can_gc))
342 }
343
344 fn is_valid_for_element(&self, input_element: &HTMLInputElement) -> bool {
345 if input_element.input_type() == InputType::Color {
346 return matches!(self, InputElementShadowTree::ColorInput(_));
347 }
348 if input_element.renders_as_text_input_widget() {
349 return matches!(self, InputElementShadowTree::TextInput(_));
350 }
351 matches!(self, InputElementShadowTree::TextValue(_))
352 }
353
354 fn update_placeholder_contents(&self, input_element: &HTMLInputElement, can_gc: CanGc) {
355 if let InputElementShadowTree::TextInput(shadow_tree) = self {
356 shadow_tree.update_placeholder(input_element, can_gc);
357 }
358 }
359
360 fn update(&self, input_element: &HTMLInputElement, can_gc: CanGc) {
361 match self {
362 InputElementShadowTree::ColorInput(shadow_tree) => {
363 shadow_tree.update(input_element, can_gc)
364 },
365 InputElementShadowTree::TextInput(shadow_tree) => shadow_tree.update(input_element),
366 InputElementShadowTree::TextValue(shadow_tree) => shadow_tree.update(input_element),
367 }
368 }
369}
370
371fn create_ua_widget_div_with_text_node(
374 document: &Document,
375 parent: &Node,
376 implemented_pseudo: PseudoElement,
377 as_first_child: bool,
378 can_gc: CanGc,
379) -> DomRoot<Element> {
380 let el = Element::create(
381 QualName::new(None, ns!(html), local_name!("div")),
382 None,
383 document,
384 ElementCreator::ScriptCreated,
385 CustomElementCreationMode::Asynchronous,
386 None,
387 can_gc,
388 );
389
390 parent
391 .upcast::<Node>()
392 .AppendChild(el.upcast::<Node>(), can_gc)
393 .unwrap();
394 el.upcast::<Node>()
395 .set_implemented_pseudo_element(implemented_pseudo);
396 let text_node = document.CreateTextNode("".into(), can_gc);
397
398 if !as_first_child {
399 el.upcast::<Node>()
400 .AppendChild(text_node.upcast::<Node>(), can_gc)
401 .unwrap();
402 } else {
403 el.upcast::<Node>()
404 .InsertBefore(
405 text_node.upcast::<Node>(),
406 el.upcast::<Node>().GetFirstChild().as_deref(),
407 can_gc,
408 )
409 .unwrap();
410 }
411 el
412}
413
414#[derive(Clone, Copy, Debug, Default, JSTraceable, PartialEq, MallocSizeOf)]
416pub(crate) enum InputType {
417 Button,
419
420 Checkbox,
422
423 Color,
425
426 Date,
428
429 DatetimeLocal,
431
432 Email,
434
435 File,
437
438 Hidden,
440
441 Image,
443
444 Month,
446
447 Number,
449
450 Password,
452
453 Radio,
455
456 Range,
458
459 Reset,
461
462 Search,
464
465 Submit,
467
468 Tel,
470
471 #[default]
473 Text,
474
475 Time,
477
478 Url,
480
481 Week,
483}
484
485impl InputType {
486 pub(crate) fn is_textual(&self) -> bool {
491 matches!(
492 *self,
493 InputType::Date |
494 InputType::DatetimeLocal |
495 InputType::Email |
496 InputType::Hidden |
497 InputType::Month |
498 InputType::Number |
499 InputType::Range |
500 InputType::Search |
501 InputType::Tel |
502 InputType::Text |
503 InputType::Time |
504 InputType::Url |
505 InputType::Week
506 )
507 }
508
509 fn is_textual_or_password(&self) -> bool {
510 self.is_textual() || *self == InputType::Password
511 }
512
513 fn has_periodic_domain(&self) -> bool {
515 *self == InputType::Time
516 }
517
518 fn as_str(&self) -> &str {
519 match *self {
520 InputType::Button => "button",
521 InputType::Checkbox => "checkbox",
522 InputType::Color => "color",
523 InputType::Date => "date",
524 InputType::DatetimeLocal => "datetime-local",
525 InputType::Email => "email",
526 InputType::File => "file",
527 InputType::Hidden => "hidden",
528 InputType::Image => "image",
529 InputType::Month => "month",
530 InputType::Number => "number",
531 InputType::Password => "password",
532 InputType::Radio => "radio",
533 InputType::Range => "range",
534 InputType::Reset => "reset",
535 InputType::Search => "search",
536 InputType::Submit => "submit",
537 InputType::Tel => "tel",
538 InputType::Text => "text",
539 InputType::Time => "time",
540 InputType::Url => "url",
541 InputType::Week => "week",
542 }
543 }
544}
545
546impl TryFrom<InputType> for InputMethodType {
547 type Error = &'static str;
548
549 fn try_from(input_type: InputType) -> Result<Self, Self::Error> {
550 match input_type {
551 InputType::Color => Ok(InputMethodType::Color),
552 InputType::Date => Ok(InputMethodType::Date),
553 InputType::DatetimeLocal => Ok(InputMethodType::DatetimeLocal),
554 InputType::Email => Ok(InputMethodType::Email),
555 InputType::Month => Ok(InputMethodType::Month),
556 InputType::Number => Ok(InputMethodType::Number),
557 InputType::Password => Ok(InputMethodType::Password),
558 InputType::Search => Ok(InputMethodType::Search),
559 InputType::Tel => Ok(InputMethodType::Tel),
560 InputType::Text => Ok(InputMethodType::Text),
561 InputType::Time => Ok(InputMethodType::Time),
562 InputType::Url => Ok(InputMethodType::Url),
563 InputType::Week => Ok(InputMethodType::Week),
564 _ => Err("Input does not support IME."),
565 }
566 }
567}
568
569impl From<&Atom> for InputType {
570 fn from(value: &Atom) -> InputType {
571 match value.to_ascii_lowercase() {
572 atom!("button") => InputType::Button,
573 atom!("checkbox") => InputType::Checkbox,
574 atom!("color") => InputType::Color,
575 atom!("date") => InputType::Date,
576 atom!("datetime-local") => InputType::DatetimeLocal,
577 atom!("email") => InputType::Email,
578 atom!("file") => InputType::File,
579 atom!("hidden") => InputType::Hidden,
580 atom!("image") => InputType::Image,
581 atom!("month") => InputType::Month,
582 atom!("number") => InputType::Number,
583 atom!("password") => InputType::Password,
584 atom!("radio") => InputType::Radio,
585 atom!("range") => InputType::Range,
586 atom!("reset") => InputType::Reset,
587 atom!("search") => InputType::Search,
588 atom!("submit") => InputType::Submit,
589 atom!("tel") => InputType::Tel,
590 atom!("text") => InputType::Text,
591 atom!("time") => InputType::Time,
592 atom!("url") => InputType::Url,
593 atom!("week") => InputType::Week,
594 _ => Self::default(),
595 }
596 }
597}
598
599#[derive(Debug, PartialEq)]
600enum ValueMode {
601 Value,
603
604 Default,
606
607 DefaultOn,
609
610 Filename,
612}
613
614#[derive(Debug, PartialEq)]
615enum StepDirection {
616 Up,
617 Down,
618}
619
620#[dom_struct]
621pub(crate) struct HTMLInputElement {
622 htmlelement: HTMLElement,
623 input_type: Cell<InputType>,
624
625 checked_changed: Cell<bool>,
627 placeholder: DomRefCell<DOMString>,
628 size: Cell<u32>,
629 maxlength: Cell<i32>,
630 minlength: Cell<i32>,
631 #[no_trace]
632 textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
633 value_dirty: Cell<bool>,
635 #[no_trace]
638 #[conditional_malloc_size_of]
639 shared_selection: SharedSelection,
640
641 filelist: MutNullableDom<FileList>,
642 form_owner: MutNullableDom<HTMLFormElement>,
643 labels_node_list: MutNullableDom<NodeList>,
644 validity_state: MutNullableDom<ValidityState>,
645 shadow_tree: DomRefCell<Option<InputElementShadowTree>>,
646 #[no_trace]
647 pending_webdriver_response: RefCell<Option<PendingWebDriverResponse>>,
648}
649
650#[derive(JSTraceable)]
651pub(crate) struct InputActivationState {
652 indeterminate: bool,
653 checked: bool,
654 checked_radio: Option<DomRoot<HTMLInputElement>>,
655 old_type: InputType,
657 }
659
660static DEFAULT_INPUT_SIZE: u32 = 20;
661static DEFAULT_MAX_LENGTH: i32 = -1;
662static DEFAULT_MIN_LENGTH: i32 = -1;
663
664#[expect(non_snake_case)]
665impl HTMLInputElement {
666 fn new_inherited(
667 local_name: LocalName,
668 prefix: Option<Prefix>,
669 document: &Document,
670 ) -> HTMLInputElement {
671 let embedder_sender = document
672 .window()
673 .as_global_scope()
674 .script_to_embedder_chan()
675 .clone();
676 HTMLInputElement {
677 htmlelement: HTMLElement::new_inherited_with_state(
678 ElementState::ENABLED | ElementState::READWRITE,
679 local_name,
680 prefix,
681 document,
682 ),
683 input_type: Cell::new(Default::default()),
684 placeholder: DomRefCell::new(DOMString::new()),
685 checked_changed: Cell::new(false),
686 maxlength: Cell::new(DEFAULT_MAX_LENGTH),
687 minlength: Cell::new(DEFAULT_MIN_LENGTH),
688 size: Cell::new(DEFAULT_INPUT_SIZE),
689 textinput: DomRefCell::new(TextInput::new(
690 Lines::Single,
691 DOMString::new(),
692 EmbedderClipboardProvider {
693 embedder_sender,
694 webview_id: document.webview_id(),
695 },
696 )),
697 value_dirty: Cell::new(false),
698 shared_selection: Default::default(),
699 filelist: MutNullableDom::new(None),
700 form_owner: Default::default(),
701 labels_node_list: MutNullableDom::new(None),
702 validity_state: Default::default(),
703 shadow_tree: Default::default(),
704 pending_webdriver_response: Default::default(),
705 }
706 }
707
708 pub(crate) fn new(
709 local_name: LocalName,
710 prefix: Option<Prefix>,
711 document: &Document,
712 proto: Option<HandleObject>,
713 can_gc: CanGc,
714 ) -> DomRoot<HTMLInputElement> {
715 Node::reflect_node_with_proto(
716 Box::new(HTMLInputElement::new_inherited(
717 local_name, prefix, document,
718 )),
719 document,
720 proto,
721 can_gc,
722 )
723 }
724
725 pub(crate) fn auto_directionality(&self) -> Option<String> {
726 match self.input_type() {
727 InputType::Text | InputType::Search | InputType::Url | InputType::Email => {
728 let value: String = self.Value().to_string();
729 Some(HTMLInputElement::directionality_from_value(&value))
730 },
731 _ => None,
732 }
733 }
734
735 pub(crate) fn directionality_from_value(value: &str) -> String {
736 if HTMLInputElement::is_first_strong_character_rtl(value) {
737 "rtl".to_owned()
738 } else {
739 "ltr".to_owned()
740 }
741 }
742
743 fn is_first_strong_character_rtl(value: &str) -> bool {
744 for ch in value.chars() {
745 return match bidi_class(ch) {
746 BidiClass::L => false,
747 BidiClass::AL => true,
748 BidiClass::R => true,
749 _ => continue,
750 };
751 }
752 false
753 }
754
755 fn value_mode(&self) -> ValueMode {
758 match self.input_type() {
759 InputType::Submit |
760 InputType::Reset |
761 InputType::Button |
762 InputType::Image |
763 InputType::Hidden => ValueMode::Default,
764
765 InputType::Checkbox | InputType::Radio => ValueMode::DefaultOn,
766
767 InputType::Color |
768 InputType::Date |
769 InputType::DatetimeLocal |
770 InputType::Email |
771 InputType::Month |
772 InputType::Number |
773 InputType::Password |
774 InputType::Range |
775 InputType::Search |
776 InputType::Tel |
777 InputType::Text |
778 InputType::Time |
779 InputType::Url |
780 InputType::Week => ValueMode::Value,
781
782 InputType::File => ValueMode::Filename,
783 }
784 }
785
786 #[inline]
787 pub(crate) fn input_type(&self) -> InputType {
788 self.input_type.get()
789 }
790
791 pub(crate) fn is_nontypeable(&self) -> bool {
793 matches!(
794 self.input_type(),
795 InputType::Button |
796 InputType::Checkbox |
797 InputType::Color |
798 InputType::File |
799 InputType::Hidden |
800 InputType::Image |
801 InputType::Radio |
802 InputType::Range |
803 InputType::Reset |
804 InputType::Submit
805 )
806 }
807
808 #[inline]
809 pub(crate) fn is_submit_button(&self) -> bool {
810 let input_type = self.input_type.get();
811 input_type == InputType::Submit || input_type == InputType::Image
812 }
813
814 fn does_minmaxlength_apply(&self) -> bool {
815 matches!(
816 self.input_type(),
817 InputType::Text |
818 InputType::Search |
819 InputType::Url |
820 InputType::Tel |
821 InputType::Email |
822 InputType::Password
823 )
824 }
825
826 fn does_pattern_apply(&self) -> bool {
827 matches!(
828 self.input_type(),
829 InputType::Text |
830 InputType::Search |
831 InputType::Url |
832 InputType::Tel |
833 InputType::Email |
834 InputType::Password
835 )
836 }
837
838 fn does_multiple_apply(&self) -> bool {
839 self.input_type() == InputType::Email
840 }
841
842 fn does_value_as_number_apply(&self) -> bool {
845 matches!(
846 self.input_type(),
847 InputType::Date |
848 InputType::Month |
849 InputType::Week |
850 InputType::Time |
851 InputType::DatetimeLocal |
852 InputType::Number |
853 InputType::Range
854 )
855 }
856
857 fn does_value_as_date_apply(&self) -> bool {
858 matches!(
859 self.input_type(),
860 InputType::Date | InputType::Month | InputType::Week | InputType::Time
861 )
862 }
863
864 fn allowed_value_step(&self) -> Option<f64> {
866 let default_step = self.default_step()?;
869
870 let Some(attribute) = self
873 .upcast::<Element>()
874 .get_attribute(&ns!(), &local_name!("step"))
875 else {
876 return Some(default_step * self.step_scale_factor());
877 };
878
879 if attribute.value().eq_ignore_ascii_case("any") {
882 return None;
883 }
884
885 let Some(parsed_value) =
889 parse_floating_point_number(&attribute.value()).filter(|value| *value > 0.0)
890 else {
891 return Some(default_step * self.step_scale_factor());
892 };
893
894 Some(parsed_value * self.step_scale_factor())
898 }
899
900 fn minimum(&self) -> Option<f64> {
902 self.upcast::<Element>()
903 .get_attribute(&ns!(), &local_name!("min"))
904 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
905 .or_else(|| self.default_minimum())
906 }
907
908 fn maximum(&self) -> Option<f64> {
910 self.upcast::<Element>()
911 .get_attribute(&ns!(), &local_name!("max"))
912 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
913 .or_else(|| self.default_maximum())
914 }
915
916 fn stepped_minimum(&self) -> Option<f64> {
919 match (self.minimum(), self.allowed_value_step()) {
920 (Some(min), Some(allowed_step)) => {
921 let step_base = self.step_base();
922 let nsteps = (min - step_base) / allowed_step;
924 Some(step_base + (allowed_step * nsteps.ceil()))
926 },
927 (_, _) => None,
928 }
929 }
930
931 fn stepped_maximum(&self) -> Option<f64> {
934 match (self.maximum(), self.allowed_value_step()) {
935 (Some(max), Some(allowed_step)) => {
936 let step_base = self.step_base();
937 let nsteps = (max - step_base) / allowed_step;
939 Some(step_base + (allowed_step * nsteps.floor()))
941 },
942 (_, _) => None,
943 }
944 }
945
946 fn default_minimum(&self) -> Option<f64> {
948 match self.input_type() {
949 InputType::Range => Some(0.0),
950 _ => None,
951 }
952 }
953
954 fn default_maximum(&self) -> Option<f64> {
956 match self.input_type() {
957 InputType::Range => Some(100.0),
958 _ => None,
959 }
960 }
961
962 fn default_range_value(&self) -> f64 {
964 let min = self.minimum().unwrap_or(0.0);
965 let max = self.maximum().unwrap_or(100.0);
966 if max < min {
967 min
968 } else {
969 min + (max - min) * 0.5
970 }
971 }
972
973 fn default_step(&self) -> Option<f64> {
975 match self.input_type() {
976 InputType::Date => Some(1.0),
977 InputType::Month => Some(1.0),
978 InputType::Week => Some(1.0),
979 InputType::Time => Some(60.0),
980 InputType::DatetimeLocal => Some(60.0),
981 InputType::Number => Some(1.0),
982 InputType::Range => Some(1.0),
983 _ => None,
984 }
985 }
986
987 fn step_scale_factor(&self) -> f64 {
989 match self.input_type() {
990 InputType::Date => 86400000.0,
991 InputType::Month => 1.0,
992 InputType::Week => 604800000.0,
993 InputType::Time => 1000.0,
994 InputType::DatetimeLocal => 1000.0,
995 InputType::Number => 1.0,
996 InputType::Range => 1.0,
997 _ => unreachable!(),
998 }
999 }
1000
1001 fn step_base(&self) -> f64 {
1003 if let Some(minimum) = self
1007 .upcast::<Element>()
1008 .get_attribute(&ns!(), &local_name!("min"))
1009 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
1010 {
1011 return minimum;
1012 }
1013
1014 if let Some(value) = self
1018 .upcast::<Element>()
1019 .get_attribute(&ns!(), &local_name!("value"))
1020 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
1021 {
1022 return value;
1023 }
1024
1025 if let Some(default_step_base) = self.default_step_base() {
1027 return default_step_base;
1028 }
1029
1030 0.0
1032 }
1033
1034 fn default_step_base(&self) -> Option<f64> {
1036 match self.input_type() {
1037 InputType::Week => Some(-259200000.0),
1038 _ => None,
1039 }
1040 }
1041
1042 fn step_up_or_down(&self, n: i32, dir: StepDirection, can_gc: CanGc) -> ErrorResult {
1046 if !self.does_value_as_number_apply() {
1049 return Err(Error::InvalidState(None));
1050 }
1051 let step_base = self.step_base();
1052
1053 let Some(allowed_value_step) = self.allowed_value_step() else {
1055 return Err(Error::InvalidState(None));
1056 };
1057
1058 let minimum = self.minimum();
1061 let maximum = self.maximum();
1062 if let (Some(min), Some(max)) = (minimum, maximum) {
1063 if min > max {
1064 return Ok(());
1065 }
1066
1067 if let Some(stepped_minimum) = self.stepped_minimum() {
1071 if stepped_minimum > max {
1072 return Ok(());
1073 }
1074 }
1075 }
1076
1077 let mut value: f64 = self
1081 .convert_string_to_number(&self.Value().str())
1082 .unwrap_or(0.0);
1083
1084 let valueBeforeStepping = value;
1086
1087 if (value - step_base) % allowed_value_step != 0.0 {
1092 value = match dir {
1093 StepDirection::Down =>
1094 {
1096 let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
1097 intervals_from_base * allowed_value_step + step_base
1098 },
1099 StepDirection::Up =>
1100 {
1102 let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
1103 intervals_from_base * allowed_value_step + step_base
1104 },
1105 };
1106 }
1107 else {
1109 value += match dir {
1114 StepDirection::Down => -f64::from(n) * allowed_value_step,
1115 StepDirection::Up => f64::from(n) * allowed_value_step,
1116 };
1117 }
1118
1119 if let Some(min) = minimum {
1123 if value < min {
1124 value = self.stepped_minimum().unwrap_or(value);
1125 }
1126 }
1127
1128 if let Some(max) = maximum {
1132 if value > max {
1133 value = self.stepped_maximum().unwrap_or(value);
1134 }
1135 }
1136
1137 match dir {
1141 StepDirection::Down => {
1142 if value > valueBeforeStepping {
1143 return Ok(());
1144 }
1145 },
1146 StepDirection::Up => {
1147 if value < valueBeforeStepping {
1148 return Ok(());
1149 }
1150 },
1151 }
1152
1153 self.SetValueAsNumber(value, can_gc)
1157 }
1158
1159 fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
1161 let list_string = self
1162 .upcast::<Element>()
1163 .get_string_attribute(&local_name!("list"));
1164 if list_string.is_empty() {
1165 return None;
1166 }
1167 let ancestor = self
1168 .upcast::<Node>()
1169 .GetRootNode(&GetRootNodeOptions::empty());
1170 let first_with_id = &ancestor
1171 .traverse_preorder(ShadowIncluding::No)
1172 .find(|node| {
1173 node.downcast::<Element>()
1174 .is_some_and(|e| e.Id() == list_string)
1175 });
1176 first_with_id
1177 .as_ref()
1178 .and_then(|el| el.downcast::<HTMLDataListElement>())
1179 .map(DomRoot::from_ref)
1180 }
1181
1182 fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
1184 match self.input_type() {
1185 InputType::Checkbox => self.Required() && !self.Checked(),
1187 InputType::Radio => {
1189 if self.radio_group_name().is_none() {
1190 return false;
1191 }
1192 let mut is_required = self.Required();
1193 let mut is_checked = self.Checked();
1194 let root = self
1195 .upcast::<Node>()
1196 .GetRootNode(&GetRootNodeOptions::empty());
1197 let form = self.form_owner();
1198 for other in radio_group_iter(
1199 self,
1200 self.radio_group_name().as_ref(),
1201 form.as_deref(),
1202 &root,
1203 ) {
1204 is_required = is_required || other.Required();
1205 is_checked = is_checked || other.Checked();
1206 }
1207 is_required && !is_checked
1208 },
1209 InputType::File => {
1211 self.Required() && self.filelist.get().is_none_or(|files| files.Length() == 0)
1212 },
1213 _ => {
1215 self.Required() &&
1216 self.value_mode() == ValueMode::Value &&
1217 self.is_mutable() &&
1218 value.is_empty()
1219 },
1220 }
1221 }
1222
1223 fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
1225 if value.is_empty() {
1226 return false;
1227 }
1228
1229 match self.input_type() {
1230 InputType::Url => Url::parse(&value.str()).is_err(),
1232 InputType::Email => {
1235 if self.Multiple() {
1236 !split_commas(&value.str()).all(|string| string.is_valid_email_address_string())
1237 } else {
1238 !value.str().is_valid_email_address_string()
1239 }
1240 },
1241 _ => false,
1243 }
1244 }
1245
1246 fn suffers_from_pattern_mismatch(&self, value: &DOMString, can_gc: CanGc) -> bool {
1248 let pattern_str = self.Pattern();
1251 if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
1252 return false;
1253 }
1254
1255 let cx = GlobalScope::get_cx();
1257 let _ac = enter_realm(self);
1258 rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>());
1259
1260 if compile_pattern(cx, &pattern_str.str(), pattern.handle_mut(), can_gc) {
1261 if self.Multiple() && self.does_multiple_apply() {
1262 !split_commas(&value.str())
1263 .all(|s| matches_js_regex(cx, pattern.handle(), s, can_gc).unwrap_or(true))
1264 } else {
1265 !matches_js_regex(cx, pattern.handle(), &value.str(), can_gc).unwrap_or(true)
1266 }
1267 } else {
1268 false
1270 }
1271 }
1272
1273 fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
1275 if value.is_empty() {
1276 return false;
1277 }
1278
1279 match self.input_type() {
1280 InputType::Email => {
1283 false
1287 },
1288 InputType::Date => !value.str().is_valid_date_string(),
1290 InputType::Month => !value.str().is_valid_month_string(),
1292 InputType::Week => !value.str().is_valid_week_string(),
1294 InputType::Time => !value.str().is_valid_time_string(),
1296 InputType::DatetimeLocal => !value.str().is_valid_local_date_time_string(),
1298 InputType::Number | InputType::Range => !value.is_valid_floating_point_number_string(),
1301 InputType::Color => !value.str().is_valid_simple_color_string(),
1303 _ => false,
1305 }
1306 }
1307
1308 fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
1311 let value_dirty = self.value_dirty.get();
1314 let textinput = self.textinput.borrow();
1315 let edit_by_user = !textinput.was_last_change_by_set_content();
1316
1317 if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
1318 return ValidationFlags::empty();
1319 }
1320
1321 let mut failed_flags = ValidationFlags::empty();
1322 let Utf16CodeUnitLength(value_len) = textinput.len_utf16();
1323 let min_length = self.MinLength();
1324 let max_length = self.MaxLength();
1325
1326 if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
1327 failed_flags.insert(ValidationFlags::TOO_SHORT);
1328 }
1329
1330 if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
1331 failed_flags.insert(ValidationFlags::TOO_LONG);
1332 }
1333
1334 failed_flags
1335 }
1336
1337 fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
1341 if value.is_empty() || !self.does_value_as_number_apply() {
1342 return ValidationFlags::empty();
1343 }
1344
1345 let Some(value_as_number) = self.convert_string_to_number(&value.str()) else {
1346 return ValidationFlags::empty();
1347 };
1348
1349 let mut failed_flags = ValidationFlags::empty();
1350 let min_value = self.minimum();
1351 let max_value = self.maximum();
1352
1353 let has_reversed_range = match (min_value, max_value) {
1355 (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
1356 _ => false,
1357 };
1358
1359 if has_reversed_range {
1360 if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
1362 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1363 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1364 }
1365 } else {
1366 if let Some(min_value) = min_value {
1368 if value_as_number < min_value {
1369 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
1370 }
1371 }
1372 if let Some(max_value) = max_value {
1374 if value_as_number > max_value {
1375 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
1376 }
1377 }
1378 }
1379
1380 if let Some(step) = self.allowed_value_step() {
1382 let diff = (self.step_base() - value_as_number) % step / value_as_number;
1386 if diff.abs() > 1e-12 {
1387 failed_flags.insert(ValidationFlags::STEP_MISMATCH);
1388 }
1389 }
1390
1391 failed_flags
1392 }
1393
1394 fn get_or_create_shadow_tree(&self, can_gc: CanGc) -> Ref<'_, InputElementShadowTree> {
1397 {
1398 if let Ok(shadow_tree) = Ref::filter_map(self.shadow_tree.borrow(), |shadow_tree| {
1399 shadow_tree
1400 .as_ref()
1401 .filter(|shadow_tree| shadow_tree.is_valid_for_element(self))
1402 }) {
1403 return shadow_tree;
1404 }
1405 }
1406 *self.shadow_tree.borrow_mut() = Some(InputElementShadowTree::new(self, can_gc));
1407 self.get_or_create_shadow_tree(can_gc)
1408 }
1409
1410 pub(crate) fn renders_as_text_input_widget(&self) -> bool {
1415 matches!(
1416 self.input_type(),
1417 InputType::Date |
1418 InputType::DatetimeLocal |
1419 InputType::Email |
1420 InputType::Month |
1421 InputType::Number |
1422 InputType::Password |
1423 InputType::Range |
1424 InputType::Search |
1425 InputType::Tel |
1426 InputType::Text |
1427 InputType::Time |
1428 InputType::Url |
1429 InputType::Week
1430 )
1431 }
1432
1433 fn may_have_embedder_control(&self) -> bool {
1434 let el = self.upcast::<Element>();
1435 self.input_type() == InputType::Color && !el.disabled_state()
1436 }
1437
1438 fn handle_key_reaction(&self, action: KeyReaction, event: &Event, can_gc: CanGc) {
1439 match action {
1440 KeyReaction::TriggerDefaultAction => {
1441 self.implicit_submission(can_gc);
1442 },
1443 KeyReaction::DispatchInput(text, is_composing, input_type) => {
1444 if event.IsTrusted() {
1445 self.textinput.borrow().queue_input_event(
1446 self.upcast(),
1447 text,
1448 is_composing,
1449 input_type,
1450 );
1451 }
1452 self.value_dirty.set(true);
1453 self.update_placeholder_shown_state();
1454 self.upcast::<Node>().dirty(NodeDamage::Other);
1455 event.mark_as_handled();
1456 },
1457 KeyReaction::RedrawSelection => {
1458 self.maybe_update_shared_selection();
1459 event.mark_as_handled();
1460 },
1461 KeyReaction::Nothing => (),
1462 }
1463 }
1464
1465 fn value_for_shadow_dom(&self) -> DOMString {
1467 let input_type = self.input_type();
1468 match input_type {
1469 InputType::Checkbox |
1470 InputType::Radio |
1471 InputType::Image |
1472 InputType::Hidden |
1473 InputType::Range => "".into(),
1474 InputType::File => {
1475 let Some(filelist) = self.filelist.get() else {
1476 return DEFAULT_FILE_INPUT_VALUE.into();
1477 };
1478 let length = filelist.Length();
1479 if length > 1 {
1480 return format!("{length} files").into();
1481 }
1482
1483 let Some(first_item) = filelist.Item(0) else {
1484 return DEFAULT_FILE_INPUT_VALUE.into();
1485 };
1486 first_item.name().to_string().into()
1487 },
1488 _ => {
1489 if let Some(attribute_value) = self
1490 .upcast::<Element>()
1491 .get_attribute(&ns!(), &local_name!("value"))
1492 .map(|attribute| attribute.Value())
1493 {
1494 return attribute_value;
1495 }
1496 match input_type {
1497 InputType::Submit => DEFAULT_SUBMIT_VALUE.into(),
1498 InputType::Reset => DEFAULT_RESET_VALUE.into(),
1499 _ => "".into(),
1500 }
1501 },
1502 }
1503 }
1504}
1505
1506pub(crate) trait LayoutHTMLInputElementHelpers<'dom> {
1507 fn size_for_layout(self) -> u32;
1508 fn selection_for_layout(self) -> Option<SharedSelection>;
1509}
1510
1511impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElement> {
1512 fn size_for_layout(self) -> u32 {
1522 self.unsafe_get().size.get()
1523 }
1524
1525 fn selection_for_layout(self) -> Option<SharedSelection> {
1526 Some(self.unsafe_get().shared_selection.clone())
1527 }
1528}
1529
1530impl TextControlElement for HTMLInputElement {
1531 fn selection_api_applies(&self) -> bool {
1533 matches!(
1534 self.input_type(),
1535 InputType::Text |
1536 InputType::Search |
1537 InputType::Url |
1538 InputType::Tel |
1539 InputType::Password
1540 )
1541 }
1542
1543 fn has_selectable_text(&self) -> bool {
1551 self.renders_as_text_input_widget() && !self.textinput.borrow().get_content().is_empty()
1552 }
1553
1554 fn has_uncollapsed_selection(&self) -> bool {
1555 self.textinput.borrow().has_uncollapsed_selection()
1556 }
1557
1558 fn set_dirty_value_flag(&self, value: bool) {
1559 self.value_dirty.set(value)
1560 }
1561
1562 fn select_all(&self) {
1563 self.textinput.borrow_mut().select_all();
1564 self.maybe_update_shared_selection();
1565 }
1566
1567 fn maybe_update_shared_selection(&self) {
1568 let offsets = self.textinput.borrow().sorted_selection_offsets_range();
1569 let (start, end) = (offsets.start.0, offsets.end.0);
1570 let range = TextByteRange::new(ByteIndex(start), ByteIndex(end));
1571 let enabled = self.renders_as_text_input_widget() && self.upcast::<Element>().focus_state();
1572
1573 let mut shared_selection = self.shared_selection.borrow_mut();
1574 if range == shared_selection.range && enabled == shared_selection.enabled {
1575 return;
1576 }
1577
1578 *shared_selection = ScriptSelection {
1579 range,
1580 character_range: self
1581 .textinput
1582 .borrow()
1583 .sorted_selection_character_offsets_range(),
1584 enabled,
1585 };
1586 self.owner_window().layout().set_needs_new_display_list();
1587 }
1588}
1589
1590impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1591 make_getter!(Accept, "accept");
1593
1594 make_setter!(SetAccept, "accept");
1596
1597 make_bool_getter!(Alpha, "alpha");
1599
1600 make_bool_setter!(SetAlpha, "alpha");
1602
1603 make_getter!(Alt, "alt");
1605
1606 make_setter!(SetAlt, "alt");
1608
1609 make_getter!(DirName, "dirname");
1611
1612 make_setter!(SetDirName, "dirname");
1614
1615 make_bool_getter!(Disabled, "disabled");
1617
1618 make_bool_setter!(SetDisabled, "disabled");
1620
1621 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1623 self.form_owner()
1624 }
1625
1626 fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1628 self.filelist.get().as_ref().cloned()
1629 }
1630
1631 fn SetFiles(&self, files: Option<&FileList>) {
1633 if self.input_type() == InputType::File && files.is_some() {
1634 self.filelist.set(files);
1635 }
1636 }
1637
1638 make_bool_getter!(DefaultChecked, "checked");
1640
1641 make_bool_setter!(SetDefaultChecked, "checked");
1643
1644 fn Checked(&self) -> bool {
1646 self.upcast::<Element>()
1647 .state()
1648 .contains(ElementState::CHECKED)
1649 }
1650
1651 fn SetChecked(&self, checked: bool, can_gc: CanGc) {
1653 self.update_checked_state(checked, true, can_gc);
1654 self.value_changed(can_gc);
1655 }
1656
1657 make_enumerated_getter!(
1659 ColorSpace,
1660 "colorspace",
1661 "limited-srgb" | "display-p3",
1662 missing => "limited-srgb",
1663 invalid => "limited-srgb"
1664 );
1665
1666 make_setter!(SetColorSpace, "colorspace");
1668
1669 make_bool_getter!(ReadOnly, "readonly");
1671
1672 make_bool_setter!(SetReadOnly, "readonly");
1674
1675 make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1677
1678 make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1680
1681 fn Type(&self) -> DOMString {
1683 DOMString::from(self.input_type().as_str())
1684 }
1685
1686 make_atomic_setter!(SetType, "type");
1688
1689 fn Value(&self) -> DOMString {
1691 match self.value_mode() {
1692 ValueMode::Value => self.textinput.borrow().get_content(),
1693 ValueMode::Default => self
1694 .upcast::<Element>()
1695 .get_attribute(&ns!(), &local_name!("value"))
1696 .map_or(DOMString::from(""), |a| {
1697 DOMString::from(a.summarize().value)
1698 }),
1699 ValueMode::DefaultOn => self
1700 .upcast::<Element>()
1701 .get_attribute(&ns!(), &local_name!("value"))
1702 .map_or(DOMString::from("on"), |a| {
1703 DOMString::from(a.summarize().value)
1704 }),
1705 ValueMode::Filename => {
1706 let mut path = DOMString::from("");
1707 match self.filelist.get() {
1708 Some(ref fl) => match fl.Item(0) {
1709 Some(ref f) => {
1710 path.push_str("C:\\fakepath\\");
1711 path.push_str(&f.name().str());
1712 path
1713 },
1714 None => path,
1715 },
1716 None => path,
1717 }
1718 },
1719 }
1720 }
1721
1722 fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult {
1724 match self.value_mode() {
1725 ValueMode::Value => {
1726 {
1727 self.value_dirty.set(true);
1729
1730 self.sanitize_value(&mut value);
1733
1734 let mut textinput = self.textinput.borrow_mut();
1735
1736 if textinput.get_content() != value {
1741 textinput.set_content(value);
1743
1744 textinput.clear_selection_to_end();
1745 }
1746 }
1747
1748 self.update_placeholder_shown_state();
1752 self.maybe_update_shared_selection();
1753 },
1754 ValueMode::Default | ValueMode::DefaultOn => {
1755 self.upcast::<Element>()
1756 .set_string_attribute(&local_name!("value"), value, can_gc);
1757 },
1758 ValueMode::Filename => {
1759 if value.is_empty() {
1760 let window = self.owner_window();
1761 let fl = FileList::new(&window, vec![], can_gc);
1762 self.filelist.set(Some(&fl));
1763 } else {
1764 return Err(Error::InvalidState(None));
1765 }
1766 },
1767 }
1768
1769 self.value_changed(can_gc);
1770 self.upcast::<Node>().dirty(NodeDamage::Other);
1771 Ok(())
1772 }
1773
1774 make_getter!(DefaultValue, "value");
1776
1777 make_setter!(SetDefaultValue, "value");
1779
1780 make_getter!(Min, "min");
1782
1783 make_setter!(SetMin, "min");
1785
1786 fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1788 self.suggestions_source_element()
1789 }
1790
1791 #[expect(unsafe_code)]
1793 fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> {
1794 self.convert_string_to_naive_datetime(self.Value())
1795 .map(|date_time| unsafe {
1796 let time = ClippedTime {
1797 t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1798 };
1799 NonNull::new_unchecked(NewDateObject(*cx, time))
1800 })
1801 }
1802
1803 #[expect(non_snake_case)]
1805 #[expect(unsafe_code)]
1806 fn SetValueAsDate(
1807 &self,
1808 cx: SafeJSContext,
1809 value: *mut JSObject,
1810 can_gc: CanGc,
1811 ) -> ErrorResult {
1812 rooted!(in(*cx) let value = value);
1813 if !self.does_value_as_date_apply() {
1814 return Err(Error::InvalidState(None));
1815 }
1816 if value.is_null() {
1817 return self.SetValue(DOMString::from(""), can_gc);
1818 }
1819 let mut msecs: f64 = 0.0;
1820 unsafe {
1824 let mut isDate = false;
1825 if !ObjectIsDate(*cx, Handle::from(value.handle()), &mut isDate) {
1826 return Err(Error::JSFailed);
1827 }
1828 if !isDate {
1829 return Err(Error::Type(c"Value was not a date".to_owned()));
1830 }
1831 if !DateGetMsecSinceEpoch(*cx, Handle::from(value.handle()), &mut msecs) {
1832 return Err(Error::JSFailed);
1833 }
1834 if !msecs.is_finite() {
1835 return self.SetValue(DOMString::from(""), can_gc);
1836 }
1837 }
1838
1839 let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1840 return self.SetValue(DOMString::from(""), can_gc);
1841 };
1842 self.SetValue(self.convert_datetime_to_dom_string(date_time), can_gc)
1843 }
1844
1845 fn ValueAsNumber(&self) -> f64 {
1847 self.convert_string_to_number(&self.Value().str())
1848 .unwrap_or(f64::NAN)
1849 }
1850
1851 fn SetValueAsNumber(&self, value: f64, can_gc: CanGc) -> ErrorResult {
1853 if value.is_infinite() {
1854 Err(Error::Type(c"value is not finite".to_owned()))
1855 } else if !self.does_value_as_number_apply() {
1856 Err(Error::InvalidState(None))
1857 } else if value.is_nan() {
1858 self.SetValue(DOMString::from(""), can_gc)
1859 } else if let Some(converted) = self.convert_number_to_string(value) {
1860 self.SetValue(converted, can_gc)
1861 } else {
1862 self.SetValue(DOMString::from(""), can_gc)
1867 }
1868 }
1869
1870 make_getter!(Name, "name");
1872
1873 make_atomic_setter!(SetName, "name");
1875
1876 make_getter!(Placeholder, "placeholder");
1878
1879 make_setter!(SetPlaceholder, "placeholder");
1881
1882 make_form_action_getter!(FormAction, "formaction");
1884
1885 make_setter!(SetFormAction, "formaction");
1887
1888 make_enumerated_getter!(
1890 FormEnctype,
1891 "formenctype",
1892 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1893 invalid => "application/x-www-form-urlencoded"
1894 );
1895
1896 make_setter!(SetFormEnctype, "formenctype");
1898
1899 make_enumerated_getter!(
1901 FormMethod,
1902 "formmethod",
1903 "get" | "post" | "dialog",
1904 invalid => "get"
1905 );
1906
1907 make_setter!(SetFormMethod, "formmethod");
1909
1910 make_getter!(FormTarget, "formtarget");
1912
1913 make_setter!(SetFormTarget, "formtarget");
1915
1916 make_bool_getter!(FormNoValidate, "formnovalidate");
1918
1919 make_bool_setter!(SetFormNoValidate, "formnovalidate");
1921
1922 make_getter!(Max, "max");
1924
1925 make_setter!(SetMax, "max");
1927
1928 make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1930
1931 make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1933
1934 make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1936
1937 make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1939
1940 make_bool_getter!(Multiple, "multiple");
1942
1943 make_bool_setter!(SetMultiple, "multiple");
1945
1946 make_getter!(Pattern, "pattern");
1948
1949 make_setter!(SetPattern, "pattern");
1951
1952 make_bool_getter!(Required, "required");
1954
1955 make_bool_setter!(SetRequired, "required");
1957
1958 make_url_getter!(Src, "src");
1960
1961 make_url_setter!(SetSrc, "src");
1963
1964 make_getter!(Step, "step");
1966
1967 make_setter!(SetStep, "step");
1969
1970 fn Indeterminate(&self) -> bool {
1972 self.upcast::<Element>()
1973 .state()
1974 .contains(ElementState::INDETERMINATE)
1975 }
1976
1977 fn SetIndeterminate(&self, val: bool) {
1979 self.upcast::<Element>()
1980 .set_state(ElementState::INDETERMINATE, val)
1981 }
1982
1983 fn GetLabels(&self, can_gc: CanGc) -> Option<DomRoot<NodeList>> {
1987 if self.input_type() == InputType::Hidden {
1988 None
1989 } else {
1990 Some(self.labels_node_list.or_init(|| {
1991 NodeList::new_labels_list(
1992 self.upcast::<Node>().owner_doc().window(),
1993 self.upcast::<HTMLElement>(),
1994 can_gc,
1995 )
1996 }))
1997 }
1998 }
1999
2000 fn Select(&self) {
2002 self.selection().dom_select();
2003 }
2004
2005 fn GetSelectionStart(&self) -> Option<u32> {
2007 self.selection().dom_start().map(|start| start.0 as u32)
2008 }
2009
2010 fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
2012 self.selection()
2013 .set_dom_start(start.map(Utf16CodeUnitLength::from))
2014 }
2015
2016 fn GetSelectionEnd(&self) -> Option<u32> {
2018 self.selection().dom_end().map(|end| end.0 as u32)
2019 }
2020
2021 fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
2023 self.selection()
2024 .set_dom_end(end.map(Utf16CodeUnitLength::from))
2025 }
2026
2027 fn GetSelectionDirection(&self) -> Option<DOMString> {
2029 self.selection().dom_direction()
2030 }
2031
2032 fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
2034 self.selection().set_dom_direction(direction)
2035 }
2036
2037 fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
2039 self.selection().set_dom_range(
2040 Utf16CodeUnitLength::from(start),
2041 Utf16CodeUnitLength::from(end),
2042 direction,
2043 )
2044 }
2045
2046 fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
2048 self.selection()
2049 .set_dom_range_text(replacement, None, None, Default::default())
2050 }
2051
2052 fn SetRangeText_(
2054 &self,
2055 replacement: DOMString,
2056 start: u32,
2057 end: u32,
2058 selection_mode: SelectionMode,
2059 ) -> ErrorResult {
2060 self.selection().set_dom_range_text(
2061 replacement,
2062 Some(Utf16CodeUnitLength::from(start)),
2063 Some(Utf16CodeUnitLength::from(end)),
2064 selection_mode,
2065 )
2066 }
2067
2068 fn SelectFiles(&self, paths: Vec<DOMString>) {
2071 if self.input_type() == InputType::File {
2072 self.select_files(Some(paths));
2073 }
2074 }
2075
2076 fn StepUp(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2078 self.step_up_or_down(n, StepDirection::Up, can_gc)
2079 }
2080
2081 fn StepDown(&self, n: i32, can_gc: CanGc) -> ErrorResult {
2083 self.step_up_or_down(n, StepDirection::Down, can_gc)
2084 }
2085
2086 fn WillValidate(&self) -> bool {
2088 self.is_instance_validatable()
2089 }
2090
2091 fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
2093 self.validity_state(can_gc)
2094 }
2095
2096 fn CheckValidity(&self, can_gc: CanGc) -> bool {
2098 self.check_validity(can_gc)
2099 }
2100
2101 fn ReportValidity(&self, can_gc: CanGc) -> bool {
2103 self.report_validity(can_gc)
2104 }
2105
2106 fn ValidationMessage(&self) -> DOMString {
2108 self.validation_message()
2109 }
2110
2111 fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
2113 self.validity_state(can_gc).set_custom_error_message(error);
2114 }
2115}
2116
2117fn radio_group_iter<'a>(
2118 elem: &'a HTMLInputElement,
2119 group: Option<&'a Atom>,
2120 form: Option<&'a HTMLFormElement>,
2121 root: &'a Node,
2122) -> impl Iterator<Item = DomRoot<HTMLInputElement>> + 'a {
2123 root.traverse_preorder(ShadowIncluding::No)
2124 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2125 .filter(move |r| &**r == elem || in_same_group(r, form, group, Some(root)))
2126}
2127
2128fn broadcast_radio_checked(broadcaster: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2129 let root = broadcaster
2130 .upcast::<Node>()
2131 .GetRootNode(&GetRootNodeOptions::empty());
2132 let form = broadcaster.form_owner();
2133 for r in radio_group_iter(broadcaster, group, form.as_deref(), &root) {
2134 if broadcaster != &*r && r.Checked() {
2135 r.SetChecked(false, can_gc);
2136 }
2137 }
2138}
2139
2140fn perform_radio_group_validation(elem: &HTMLInputElement, group: Option<&Atom>, can_gc: CanGc) {
2141 let root = elem
2142 .upcast::<Node>()
2143 .GetRootNode(&GetRootNodeOptions::empty());
2144 let form = elem.form_owner();
2145 for r in radio_group_iter(elem, group, form.as_deref(), &root) {
2146 r.validity_state(can_gc)
2147 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2148 }
2149}
2150
2151fn in_same_group(
2153 other: &HTMLInputElement,
2154 owner: Option<&HTMLFormElement>,
2155 group: Option<&Atom>,
2156 tree_root: Option<&Node>,
2157) -> bool {
2158 if group.is_none() {
2159 return false;
2161 }
2162
2163 if other.input_type() != InputType::Radio ||
2164 other.form_owner().as_deref() != owner ||
2165 other.radio_group_name().as_ref() != group
2166 {
2167 return false;
2168 }
2169
2170 match tree_root {
2171 Some(tree_root) => {
2172 let other_root = other
2173 .upcast::<Node>()
2174 .GetRootNode(&GetRootNodeOptions::empty());
2175 tree_root == &*other_root
2176 },
2177 None => {
2178 true
2180 },
2181 }
2182}
2183
2184impl HTMLInputElement {
2185 fn radio_group_updated(&self, group: Option<&Atom>, can_gc: CanGc) {
2186 if self.Checked() {
2187 broadcast_radio_checked(self, group, can_gc);
2188 }
2189 }
2190
2191 pub(crate) fn form_datums(
2194 &self,
2195 submitter: Option<FormSubmitterElement>,
2196 encoding: Option<&'static Encoding>,
2197 ) -> Vec<FormDatum> {
2198 let ty = self.Type();
2202
2203 let name = self.Name();
2205 let is_submitter = match submitter {
2206 Some(FormSubmitterElement::Input(s)) => self == s,
2207 _ => false,
2208 };
2209
2210 match self.input_type() {
2211 InputType::Submit | InputType::Button | InputType::Reset if !is_submitter => {
2213 return vec![];
2214 },
2215
2216 InputType::Radio | InputType::Checkbox => {
2218 if !self.Checked() || name.is_empty() {
2219 return vec![];
2220 }
2221 },
2222
2223 InputType::File => {
2224 let mut datums = vec![];
2225
2226 let name = self.Name();
2228
2229 match self.GetFiles() {
2230 Some(fl) => {
2231 for f in fl.iter_files() {
2232 datums.push(FormDatum {
2233 ty: ty.clone(),
2234 name: name.clone(),
2235 value: FormDatumValue::File(DomRoot::from_ref(f)),
2236 });
2237 }
2238 },
2239 None => {
2240 datums.push(FormDatum {
2241 ty: ty.clone(),
2244 name: name.clone(),
2245 value: FormDatumValue::String(DOMString::from("")),
2246 })
2247 },
2248 }
2249
2250 return datums;
2251 },
2252
2253 InputType::Image => return vec![], InputType::Hidden => {
2257 if name.to_ascii_lowercase() == "_charset_" {
2258 return vec![FormDatum {
2259 ty: ty.clone(),
2260 name,
2261 value: FormDatumValue::String(match encoding {
2262 None => DOMString::from("UTF-8"),
2263 Some(enc) => DOMString::from(enc.name()),
2264 }),
2265 }];
2266 }
2267 },
2268
2269 _ => {
2271 if name.is_empty() {
2272 return vec![];
2273 }
2274 },
2275 }
2276
2277 vec![FormDatum {
2279 ty: ty.clone(),
2280 name,
2281 value: FormDatumValue::String(self.Value()),
2282 }]
2283 }
2284
2285 fn radio_group_name(&self) -> Option<Atom> {
2287 self.upcast::<Element>()
2288 .get_name()
2289 .filter(|name| !name.is_empty())
2290 }
2291
2292 fn update_checked_state(&self, checked: bool, dirty: bool, can_gc: CanGc) {
2293 self.upcast::<Element>()
2294 .set_state(ElementState::CHECKED, checked);
2295
2296 if dirty {
2297 self.checked_changed.set(true);
2298 }
2299
2300 if self.input_type() == InputType::Radio && checked {
2301 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
2302 }
2303
2304 self.upcast::<Node>().dirty(NodeDamage::Other);
2305 }
2306
2307 pub(crate) fn is_mutable(&self) -> bool {
2309 !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
2312 }
2313
2314 pub(crate) fn reset(&self, can_gc: CanGc) {
2324 self.value_dirty.set(false);
2325
2326 let mut value = self.DefaultValue();
2328 self.sanitize_value(&mut value);
2329 self.textinput.borrow_mut().set_content(value);
2330
2331 let input_type = self.input_type.get();
2332 if matches!(input_type, InputType::Radio | InputType::Checkbox) {
2333 self.update_checked_state(self.DefaultChecked(), false, can_gc);
2334 self.checked_changed.set(false);
2335 }
2336
2337 if input_type == InputType::File {
2338 self.filelist
2339 .set(Some(&FileList::new(&self.owner_window(), vec![], can_gc)));
2340 } else {
2341 self.filelist.set(None);
2342 }
2343
2344 self.value_changed(can_gc);
2345 }
2346
2347 pub(crate) fn clear(&self, can_gc: CanGc) {
2350 self.value_dirty.set(false);
2352 self.checked_changed.set(false);
2353 self.textinput.borrow_mut().set_content(DOMString::from(""));
2355 self.update_checked_state(self.DefaultChecked(), false, can_gc);
2357 if self.filelist.get().is_some() {
2359 let window = self.owner_window();
2360 let filelist = FileList::new(&window, vec![], can_gc);
2361 self.filelist.set(Some(&filelist));
2362 }
2363
2364 {
2367 let mut textinput = self.textinput.borrow_mut();
2368 let mut value = textinput.get_content();
2369 self.sanitize_value(&mut value);
2370 textinput.set_content(value);
2371 }
2372
2373 self.value_changed(can_gc);
2374 }
2375
2376 fn update_placeholder_shown_state(&self) {
2377 if !self.input_type().is_textual_or_password() {
2378 self.upcast::<Element>().set_placeholder_shown_state(false);
2379 } else {
2380 let has_placeholder = !self.placeholder.borrow().is_empty();
2381 let has_value = !self.textinput.borrow().is_empty();
2382 self.upcast::<Element>()
2383 .set_placeholder_shown_state(has_placeholder && !has_value);
2384 }
2385 }
2386
2387 pub(crate) fn select_files_for_webdriver(
2388 &self,
2389 test_paths: Vec<DOMString>,
2390 response_sender: GenericSender<Result<bool, ErrorStatus>>,
2391 ) {
2392 let mut stored_sender = self.pending_webdriver_response.borrow_mut();
2393 assert!(stored_sender.is_none());
2394
2395 *stored_sender = Some(PendingWebDriverResponse {
2396 response_sender,
2397 expected_file_count: test_paths.len(),
2398 });
2399
2400 self.select_files(Some(test_paths));
2401 }
2402
2403 pub(crate) fn select_files(&self, test_paths: Option<Vec<DOMString>>) {
2407 let current_paths = match &test_paths {
2408 Some(test_paths) => test_paths
2409 .iter()
2410 .filter_map(|path_str| PathBuf::from_str(&path_str.str()).ok())
2411 .collect(),
2412 None => Default::default(),
2415 };
2416
2417 let accept_current_paths_for_testing = test_paths.is_some();
2418 self.owner_document()
2419 .embedder_controls()
2420 .show_embedder_control(
2421 ControlElement::FileInput(DomRoot::from_ref(self)),
2422 EmbedderControlRequest::FilePicker(FilePickerRequest {
2423 origin: self.owner_window().origin().immutable().clone(),
2424 current_paths,
2425 filter_patterns: filter_from_accept(&self.Accept()),
2426 allow_select_multiple: self.Multiple(),
2427 accept_current_paths_for_testing,
2428 }),
2429 None,
2430 );
2431 }
2432
2433 fn sanitize_value(&self, value: &mut DOMString) {
2435 match self.input_type() {
2436 InputType::Text | InputType::Search | InputType::Tel | InputType::Password => {
2437 value.strip_newlines();
2438 },
2439 InputType::Url => {
2440 value.strip_newlines();
2441 value.strip_leading_and_trailing_ascii_whitespace();
2442 },
2443 InputType::Date => {
2444 if !value.str().is_valid_date_string() {
2445 value.clear();
2446 }
2447 },
2448 InputType::Month => {
2449 if !value.str().is_valid_month_string() {
2450 value.clear();
2451 }
2452 },
2453 InputType::Week => {
2454 if !value.str().is_valid_week_string() {
2455 value.clear();
2456 }
2457 },
2458 InputType::Color => {
2459 self.update_a_color_well_control_color(value);
2462 },
2463 InputType::Time => {
2464 if !value.str().is_valid_time_string() {
2465 value.clear();
2466 }
2467 },
2468 InputType::DatetimeLocal => {
2469 let time = value
2470 .str()
2471 .parse_local_date_time_string()
2472 .map(|date_time| date_time.to_local_date_time_string());
2473 match time {
2474 Some(normalized_string) => *value = DOMString::from_string(normalized_string),
2475 None => value.clear(),
2476 }
2477 },
2478 InputType::Number => {
2479 if !value.is_valid_floating_point_number_string() {
2480 value.clear();
2481 }
2482 },
2489 InputType::Range => {
2491 if !value.is_valid_floating_point_number_string() {
2492 *value = DOMString::from(self.default_range_value().to_string());
2493 }
2494 if let Ok(fval) = &value.parse::<f64>() {
2495 let mut fval = *fval;
2496 if let Some(max) = self.maximum() {
2499 if fval > max {
2500 fval = max;
2501 }
2502 }
2503 if let Some(min) = self.minimum() {
2504 if fval < min {
2505 fval = min;
2506 }
2507 }
2508 if let Some(allowed_value_step) = self.allowed_value_step() {
2513 let step_base = self.step_base();
2514 let steps_from_base = (fval - step_base) / allowed_value_step;
2515 if steps_from_base.fract() != 0.0 {
2516 let int_steps = round_halves_positive(steps_from_base);
2519 fval = int_steps * allowed_value_step + step_base;
2521
2522 if let Some(stepped_maximum) = self.stepped_maximum() {
2526 if fval > stepped_maximum {
2527 fval = stepped_maximum;
2528 }
2529 }
2530 if let Some(stepped_minimum) = self.stepped_minimum() {
2531 if fval < stepped_minimum {
2532 fval = stepped_minimum;
2533 }
2534 }
2535 }
2536 }
2537 *value = DOMString::from(fval.to_string());
2538 };
2539 },
2540 InputType::Email => {
2541 if !self.Multiple() {
2542 value.strip_newlines();
2543 value.strip_leading_and_trailing_ascii_whitespace();
2544 } else {
2545 let sanitized = split_commas(&value.str())
2546 .map(|token| {
2547 let mut token = DOMString::from(token.to_string());
2548 token.strip_newlines();
2549 token.strip_leading_and_trailing_ascii_whitespace();
2550 token
2551 })
2552 .join(",");
2553 value.clear();
2554 value.push_str(sanitized.as_str());
2555 }
2556 },
2557 InputType::Button |
2560 InputType::Checkbox |
2561 InputType::File |
2562 InputType::Hidden |
2563 InputType::Image |
2564 InputType::Radio |
2565 InputType::Reset |
2566 InputType::Submit => (),
2567 }
2568 }
2569
2570 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
2571 fn selection(&self) -> TextControlSelection<'_, Self> {
2572 TextControlSelection::new(self, &self.textinput)
2573 }
2574
2575 fn update_a_color_well_control_color(&self, element_value: &mut DOMString) {
2577 debug_assert_eq!(self.input_type(), InputType::Color);
2579
2580 let value = element_value.to_owned();
2586
2587 let color = parse_color_value(
2590 &value.str(),
2591 self.owner_document().url().as_url().to_owned(),
2592 );
2593
2594 self.serialize_a_color_well_control_color(color, element_value);
2597 }
2598
2599 fn colorspace(&self) -> ColorSpace {
2601 let colorspace = self
2602 .upcast::<Element>()
2603 .get_string_attribute(&local_name!("colorspace"));
2604 if colorspace.str() == "display-p3" {
2605 ColorSpace::DisplayP3
2606 } else {
2607 ColorSpace::Srgb
2608 }
2609 }
2610
2611 fn serialize_a_color_well_control_color(
2613 &self,
2614 mut color: AbsoluteColor,
2615 destination: &mut DOMString,
2616 ) {
2617 debug_assert_eq!(self.input_type(), InputType::Color);
2619
2620 let mut html_compatible = false;
2622
2623 let has_alpha = self.Alpha();
2625 if !has_alpha {
2626 color.alpha = 1.0;
2627 }
2628
2629 let colorspace_attribute = self.colorspace();
2631 if colorspace_attribute == ColorSpace::Srgb {
2632 color = color.to_color_space(ColorSpace::Srgb);
2634
2635 color.components.0 = color.components.0.clamp(0.0, 1.0);
2638 color.components.1 = color.components.1.clamp(0.0, 1.0);
2639 color.components.2 = color.components.2.clamp(0.0, 1.0);
2640
2641 if !has_alpha {
2643 html_compatible = true;
2644 }
2645 else {
2648 color.flags &= !ColorFlags::IS_LEGACY_SRGB;
2649 }
2650 }
2651 else {
2653 debug_assert_eq!(colorspace_attribute, ColorSpace::DisplayP3);
2655
2656 color = color.to_color_space(ColorSpace::DisplayP3);
2658 }
2659
2660 *destination = if html_compatible {
2663 color = color.to_color_space(ColorSpace::Srgb);
2664 format!(
2665 "#{:0>2x}{:0>2x}{:0>2x}",
2666 (color.components.0 * 255.0).round() as usize,
2667 (color.components.1 * 255.0).round() as usize,
2668 (color.components.2 * 255.0).round() as usize
2669 )
2670 } else {
2671 color.to_css_string()
2672 }
2673 .into();
2674 }
2675
2676 fn implicit_submission(&self, can_gc: CanGc) {
2678 let doc = self.owner_document();
2679 let node = doc.upcast::<Node>();
2680 let owner = self.form_owner();
2681 let form = match owner {
2682 None => return,
2683 Some(ref f) => f,
2684 };
2685
2686 if self.upcast::<Element>().click_in_progress() {
2687 return;
2688 }
2689 let submit_button = node
2690 .query_selector_iter(DOMString::from("input[type=submit]"))
2691 .unwrap()
2692 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2693 .find(|r| r.form_owner() == owner);
2694 match submit_button {
2695 Some(ref button) => {
2696 if button.is_instance_activatable() {
2697 button
2700 .upcast::<Node>()
2701 .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
2702 }
2703 },
2704 None => {
2705 let mut inputs = node
2706 .query_selector_iter(DOMString::from("input"))
2707 .unwrap()
2708 .filter_map(DomRoot::downcast::<HTMLInputElement>)
2709 .filter(|input| {
2710 input.form_owner() == owner &&
2711 matches!(
2712 input.input_type(),
2713 InputType::Text |
2714 InputType::Search |
2715 InputType::Url |
2716 InputType::Tel |
2717 InputType::Email |
2718 InputType::Password |
2719 InputType::Date |
2720 InputType::Month |
2721 InputType::Week |
2722 InputType::Time |
2723 InputType::DatetimeLocal |
2724 InputType::Number
2725 )
2726 });
2727
2728 if inputs.nth(1).is_some() {
2729 return;
2731 }
2732 form.submit(
2733 SubmittedFrom::NotFromForm,
2734 FormSubmitterElement::Form(form),
2735 can_gc,
2736 );
2737 },
2738 }
2739 }
2740
2741 fn convert_string_to_number(&self, value: &str) -> Option<f64> {
2743 match self.input_type() {
2744 InputType::Date => value.parse_date_string().map(|date_time| {
2751 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2752 }),
2753 InputType::Month => value.parse_month_string().map(|date_time| {
2762 ((date_time.year() - 1970) * 12) as f64 + (date_time.month() as u8 - 1) as f64
2763 }),
2764 InputType::Week => value.parse_week_string().map(|date_time| {
2771 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2772 }),
2773 InputType::Time => value
2778 .parse_time_string()
2779 .map(|date_time| (date_time.time() - Time::MIDNIGHT).whole_milliseconds() as f64),
2780 InputType::DatetimeLocal => value.parse_local_date_time_string().map(|date_time| {
2787 (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64
2788 }),
2789 InputType::Number | InputType::Range => parse_floating_point_number(value),
2790 _ => None,
2793 }
2794 }
2795
2796 fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
2798 match self.input_type() {
2799 InputType::Date | InputType::Week | InputType::Time | InputType::DatetimeLocal => {
2800 OffsetDateTime::from_unix_timestamp_nanos((value * 1e6) as i128)
2801 .ok()
2802 .map(|value| self.convert_datetime_to_dom_string(value))
2803 },
2804 InputType::Month => {
2805 let date = OffsetDateTime::UNIX_EPOCH;
2809 let years = (value / 12.) as i32;
2810 let year = date.year() + years;
2811
2812 let months = value as i32 - (years * 12);
2813 let months = match months.cmp(&0) {
2814 Ordering::Less => (12 - months) as u8,
2815 Ordering::Equal | Ordering::Greater => months as u8,
2816 } + 1;
2817
2818 let date = date
2819 .replace_year(year)
2820 .ok()?
2821 .replace_month(Month::try_from(months).ok()?)
2822 .ok()?;
2823 Some(self.convert_datetime_to_dom_string(date))
2824 },
2825 InputType::Number | InputType::Range => {
2826 let mut value = DOMString::from(value.to_string());
2827 value.set_best_representation_of_the_floating_point_number();
2828 Some(value)
2829 },
2830 _ => unreachable!("Should not have called convert_number_to_string for non-Date types"),
2831 }
2832 }
2833
2834 fn convert_string_to_naive_datetime(&self, value: DOMString) -> Option<OffsetDateTime> {
2838 match self.input_type() {
2839 InputType::Date => value.str().parse_date_string(),
2840 InputType::Time => value.str().parse_time_string(),
2841 InputType::Week => value.str().parse_week_string(),
2842 InputType::Month => value.str().parse_month_string(),
2843 InputType::DatetimeLocal => value.str().parse_local_date_time_string(),
2844 _ => None,
2846 }
2847 }
2848
2849 fn convert_datetime_to_dom_string(&self, value: OffsetDateTime) -> DOMString {
2853 DOMString::from_string(match self.input_type() {
2854 InputType::Date => value.to_date_string(),
2855 InputType::Month => value.to_month_string(),
2856 InputType::Week => value.to_week_string(),
2857 InputType::Time => value.to_time_string(),
2858 InputType::DatetimeLocal => value.to_local_date_time_string(),
2859 _ => {
2860 unreachable!("Should not have called convert_datetime_to_string for non-Date types")
2861 },
2862 })
2863 }
2864
2865 fn update_related_validity_states(&self, can_gc: CanGc) {
2866 match self.input_type() {
2867 InputType::Radio => {
2868 perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc)
2869 },
2870 _ => {
2871 self.validity_state(can_gc)
2872 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2873 },
2874 }
2875 }
2876
2877 fn value_changed(&self, can_gc: CanGc) {
2878 self.maybe_update_shared_selection();
2879 self.update_related_validity_states(can_gc);
2880 self.get_or_create_shadow_tree(can_gc).update(self, can_gc);
2881 }
2882
2883 fn show_the_picker_if_applicable(&self) {
2885 if !self.is_mutable() {
2889 return;
2890 }
2891
2892 if self.input_type() == InputType::Color {
2895 let document = self.owner_document();
2896 let current_value = self.Value();
2897 let current_color = parse_color_value(
2898 ¤t_value.str(),
2899 self.owner_document().url().as_url().to_owned(),
2900 )
2901 .to_color_space(ColorSpace::Srgb);
2902 let current_color = RgbColor {
2903 red: (current_color.components.0 * 255.0).round() as u8,
2904 green: (current_color.components.1 * 255.0).round() as u8,
2905 blue: (current_color.components.2 * 255.0).round() as u8,
2906 };
2907 document.embedder_controls().show_embedder_control(
2908 ControlElement::ColorInput(DomRoot::from_ref(self)),
2909 EmbedderControlRequest::ColorPicker(current_color),
2910 None,
2911 );
2912 }
2913 }
2914
2915 pub(crate) fn handle_color_picker_response(&self, response: Option<RgbColor>, can_gc: CanGc) {
2916 let Some(selected_color) = response else {
2917 return;
2918 };
2919
2920 let formatted_color = format!(
2921 "#{:0>2x}{:0>2x}{:0>2x}",
2922 selected_color.red, selected_color.green, selected_color.blue
2923 );
2924 let _ = self.SetValue(formatted_color.into(), can_gc);
2925 }
2926
2927 pub(crate) fn handle_file_picker_response(
2928 &self,
2929 response: Option<Vec<SelectedFile>>,
2930 can_gc: CanGc,
2931 ) {
2932 let mut files = Vec::new();
2933
2934 if let Some(pending_webdriver_reponse) = self.pending_webdriver_response.borrow_mut().take()
2935 {
2936 if self.Multiple() {
2943 if let Some(filelist) = self.filelist.get() {
2944 files = filelist.iter_files().map(|file| file.as_rooted()).collect();
2945 }
2946 }
2947
2948 let number_files_selected = response.as_ref().map(Vec::len).unwrap_or_default();
2949 pending_webdriver_reponse.finish(number_files_selected);
2950 }
2951
2952 let Some(response_files) = response else {
2953 return;
2954 };
2955
2956 let window = self.owner_window();
2957 files.extend(
2958 response_files
2959 .into_iter()
2960 .map(|file| File::new_from_selected(&window, file, can_gc)),
2961 );
2962
2963 if !self.Multiple() {
2966 files = files
2967 .pop()
2968 .map(|last_file| vec![last_file])
2969 .unwrap_or_default();
2970 }
2971
2972 self.filelist
2973 .set(Some(&FileList::new(&window, files, can_gc)));
2974
2975 let target = self.upcast::<EventTarget>();
2976 target.fire_event_with_params(
2977 atom!("input"),
2978 EventBubbles::Bubbles,
2979 EventCancelable::NotCancelable,
2980 EventComposed::Composed,
2981 can_gc,
2982 );
2983 target.fire_bubbling_event(atom!("change"), can_gc);
2984 }
2985
2986 fn handle_focus_event(&self, event: &FocusEvent) {
2987 let event_type = event.upcast::<Event>().type_();
2988 if *event_type == *"blur" {
2989 self.owner_document()
2990 .embedder_controls()
2991 .hide_embedder_control(self.upcast());
2992 } else if *event_type == *"focus" {
2993 let Ok(input_method_type) = self.input_type().try_into() else {
2994 return;
2995 };
2996
2997 self.owner_document()
2998 .embedder_controls()
2999 .show_embedder_control(
3000 ControlElement::Ime(DomRoot::from_ref(self.upcast())),
3001 EmbedderControlRequest::InputMethod(InputMethodRequest {
3002 input_method_type,
3003 text: self.Value().to_string(),
3004 insertion_point: self.GetSelectionEnd(),
3005 multiline: false,
3006 }),
3007 None,
3008 );
3009 } else {
3010 unreachable!("Got unexpected FocusEvent {event_type:?}");
3011 }
3012 }
3013
3014 fn handle_mouse_event(&self, mouse_event: &MouseEvent) {
3015 if mouse_event.upcast::<Event>().DefaultPrevented() {
3016 return;
3017 }
3018
3019 if !self.input_type().is_textual_or_password() || self.textinput.borrow().is_empty() {
3022 return;
3023 }
3024 let node = self.upcast();
3025 if self
3026 .textinput
3027 .borrow_mut()
3028 .handle_mouse_event(node, mouse_event)
3029 {
3030 self.maybe_update_shared_selection();
3031 }
3032 }
3033}
3034
3035impl VirtualMethods for HTMLInputElement {
3036 fn super_type(&self) -> Option<&dyn VirtualMethods> {
3037 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
3038 }
3039
3040 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
3041 let could_have_had_embedder_control = self.may_have_embedder_control();
3042
3043 self.super_type()
3044 .unwrap()
3045 .attribute_mutated(attr, mutation, can_gc);
3046
3047 match *attr.local_name() {
3048 local_name!("disabled") => {
3049 let disabled_state = match mutation {
3050 AttributeMutation::Set(None, _) => true,
3051 AttributeMutation::Set(Some(_), _) => {
3052 return;
3054 },
3055 AttributeMutation::Removed => false,
3056 };
3057 let el = self.upcast::<Element>();
3058 el.set_disabled_state(disabled_state);
3059 el.set_enabled_state(!disabled_state);
3060 el.check_ancestors_disabled_state_for_form_control();
3061
3062 if self.input_type().is_textual() {
3063 let read_write = !(self.ReadOnly() || el.disabled_state());
3064 el.set_read_write_state(read_write);
3065 }
3066
3067 el.update_sequentially_focusable_status(can_gc);
3068 },
3069 local_name!("checked") if !self.checked_changed.get() => {
3070 let checked_state = match mutation {
3071 AttributeMutation::Set(None, _) => true,
3072 AttributeMutation::Set(Some(_), _) => {
3073 return;
3075 },
3076 AttributeMutation::Removed => false,
3077 };
3078 self.update_checked_state(checked_state, false, can_gc);
3079 },
3080 local_name!("size") => {
3081 let size = mutation.new_value(attr).map(|value| value.as_uint());
3082 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
3083 },
3084 local_name!("type") => {
3085 let el = self.upcast::<Element>();
3086 match mutation {
3087 AttributeMutation::Set(..) => {
3088 let new_type = InputType::from(attr.value().as_atom());
3089
3090 let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
3092 let previously_selectable = self.selection_api_applies();
3093
3094 self.input_type.set(new_type);
3095
3096 if new_type.is_textual() {
3097 let read_write = !(self.ReadOnly() || el.disabled_state());
3098 el.set_read_write_state(read_write);
3099 } else {
3100 el.set_read_write_state(false);
3101 }
3102
3103 if new_type == InputType::File {
3104 let window = self.owner_window();
3105 let filelist = FileList::new(&window, vec![], can_gc);
3106 self.filelist.set(Some(&filelist));
3107 }
3108
3109 let new_value_mode = self.value_mode();
3110 match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
3111 (&ValueMode::Value, false, ValueMode::Default) |
3113 (&ValueMode::Value, false, ValueMode::DefaultOn) => {
3114 self.SetValue(old_idl_value, can_gc)
3115 .expect("Failed to set input value on type change to a default ValueMode.");
3116 },
3117
3118 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
3120 self.SetValue(
3121 self.upcast::<Element>()
3122 .get_attribute(&ns!(), &local_name!("value"))
3123 .map_or(DOMString::from(""), |a| {
3124 DOMString::from(a.summarize().value)
3125 }),
3126 can_gc,
3127 )
3128 .expect(
3129 "Failed to set input value on type change to ValueMode::Value.",
3130 );
3131 self.value_dirty.set(false);
3132 },
3133
3134 (_, _, ValueMode::Filename)
3136 if old_value_mode != ValueMode::Filename =>
3137 {
3138 self.SetValue(DOMString::from(""), can_gc)
3139 .expect("Failed to set input value on type change to ValueMode::Filename.");
3140 },
3141 _ => {},
3142 }
3143
3144 if new_type == InputType::Radio {
3146 self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3147 }
3148
3149 let mut textinput = self.textinput.borrow_mut();
3151 let mut value = textinput.get_content();
3152 self.sanitize_value(&mut value);
3153 textinput.set_content(value);
3154 self.upcast::<Node>().dirty(NodeDamage::Other);
3155
3156 if !previously_selectable && self.selection_api_applies() {
3158 textinput.clear_selection_to_start();
3159 }
3160 },
3161 AttributeMutation::Removed => {
3162 if self.input_type() == InputType::Radio {
3163 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
3164 }
3165 self.input_type.set(InputType::default());
3166 let el = self.upcast::<Element>();
3167
3168 let read_write = !(self.ReadOnly() || el.disabled_state());
3169 el.set_read_write_state(read_write);
3170 },
3171 }
3172
3173 self.update_placeholder_shown_state();
3174 self.get_or_create_shadow_tree(can_gc)
3175 .update_placeholder_contents(self, can_gc);
3176 },
3177 local_name!("value") if !self.value_dirty.get() => {
3178 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
3182 let mut value = value.map_or(DOMString::new(), DOMString::from);
3183
3184 self.sanitize_value(&mut value);
3185 self.textinput.borrow_mut().set_content(value);
3186 self.update_placeholder_shown_state();
3187 },
3188 local_name!("name") if self.input_type() == InputType::Radio => {
3189 self.radio_group_updated(
3190 mutation.new_value(attr).as_ref().map(|name| name.as_atom()),
3191 can_gc,
3192 );
3193 },
3194 local_name!("maxlength") => match *attr.value() {
3195 AttrValue::Int(_, value) => {
3196 let mut textinput = self.textinput.borrow_mut();
3197
3198 if value < 0 {
3199 textinput.set_max_length(None);
3200 } else {
3201 textinput.set_max_length(Some(Utf16CodeUnitLength(value as usize)))
3202 }
3203 },
3204 _ => panic!("Expected an AttrValue::Int"),
3205 },
3206 local_name!("minlength") => match *attr.value() {
3207 AttrValue::Int(_, value) => {
3208 let mut textinput = self.textinput.borrow_mut();
3209
3210 if value < 0 {
3211 textinput.set_min_length(None);
3212 } else {
3213 textinput.set_min_length(Some(Utf16CodeUnitLength(value as usize)))
3214 }
3215 },
3216 _ => panic!("Expected an AttrValue::Int"),
3217 },
3218 local_name!("placeholder") => {
3219 {
3220 let mut placeholder = self.placeholder.borrow_mut();
3221 placeholder.clear();
3222 if let AttributeMutation::Set(..) = mutation {
3223 placeholder
3224 .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
3225 }
3226 }
3227 self.update_placeholder_shown_state();
3228 self.get_or_create_shadow_tree(can_gc)
3229 .update_placeholder_contents(self, can_gc);
3230 },
3231 local_name!("readonly") => {
3232 if self.input_type().is_textual() {
3233 let el = self.upcast::<Element>();
3234 match mutation {
3235 AttributeMutation::Set(..) => {
3236 el.set_read_write_state(false);
3237 },
3238 AttributeMutation::Removed => {
3239 el.set_read_write_state(!el.disabled_state());
3240 },
3241 }
3242 }
3243 },
3244 local_name!("form") => {
3245 self.form_attribute_mutated(mutation, can_gc);
3246 },
3247 local_name!("alpha") | local_name!("colorspace") => {
3248 let mut textinput = self.textinput.borrow_mut();
3252 let mut value = textinput.get_content();
3253 self.update_a_color_well_control_color(&mut value);
3254 textinput.set_content(value);
3255 },
3256 _ => {},
3257 }
3258
3259 self.value_changed(can_gc);
3260
3261 if could_have_had_embedder_control && !self.may_have_embedder_control() {
3262 self.owner_document()
3263 .embedder_controls()
3264 .hide_embedder_control(self.upcast());
3265 }
3266 }
3267
3268 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
3269 match *name {
3270 local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
3271 local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
3272 local_name!("type") => AttrValue::from_atomic(value.into()),
3273 local_name!("maxlength") => {
3274 AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
3275 },
3276 local_name!("minlength") => {
3277 AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
3278 },
3279 _ => self
3280 .super_type()
3281 .unwrap()
3282 .parse_plain_attribute(name, value),
3283 }
3284 }
3285
3286 fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
3287 if let Some(s) = self.super_type() {
3288 s.bind_to_tree(context, can_gc);
3289 }
3290 self.upcast::<Element>()
3291 .check_ancestors_disabled_state_for_form_control();
3292
3293 if self.input_type() == InputType::Radio {
3294 self.radio_group_updated(self.radio_group_name().as_ref(), can_gc);
3295 }
3296
3297 self.value_changed(can_gc);
3298 }
3299
3300 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
3301 let form_owner = self.form_owner();
3302
3303 self.super_type().unwrap().unbind_from_tree(context, can_gc);
3304
3305 let node = self.upcast::<Node>();
3306 let el = self.upcast::<Element>();
3307 if node
3308 .ancestors()
3309 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
3310 {
3311 el.check_ancestors_disabled_state_for_form_control();
3312 } else {
3313 el.check_disabled_attribute();
3314 }
3315
3316 if self.input_type() == InputType::Radio {
3317 let root = context.parent.GetRootNode(&GetRootNodeOptions::empty());
3318 for r in radio_group_iter(
3319 self,
3320 self.radio_group_name().as_ref(),
3321 form_owner.as_deref(),
3322 &root,
3323 ) {
3324 r.validity_state(can_gc)
3325 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3326 }
3327 }
3328
3329 self.validity_state(can_gc)
3330 .perform_validation_and_update(ValidationFlags::all(), can_gc);
3331
3332 if self.input_type() == InputType::Color {
3333 self.owner_document()
3334 .embedder_controls()
3335 .hide_embedder_control(self.upcast());
3336 }
3337 }
3338
3339 fn handle_event(&self, event: &Event, can_gc: CanGc) {
3345 if let Some(s) = self.super_type() {
3346 s.handle_event(event, can_gc);
3347 }
3348
3349 if let Some(mouse_event) = event.downcast::<MouseEvent>() {
3350 self.handle_mouse_event(mouse_event);
3351 } else if event.type_() == atom!("keydown") &&
3352 !event.DefaultPrevented() &&
3353 self.input_type().is_textual_or_password()
3354 {
3355 if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
3356 let action = self.textinput.borrow_mut().handle_keydown(keyevent);
3359 self.handle_key_reaction(action, event, can_gc);
3360 }
3361 } else if event.type_() == atom!("keypress") &&
3362 !event.DefaultPrevented() &&
3363 self.input_type().is_textual_or_password()
3364 {
3365 } else if (event.type_() == atom!("compositionstart") ||
3369 event.type_() == atom!("compositionupdate") ||
3370 event.type_() == atom!("compositionend")) &&
3371 self.input_type().is_textual_or_password()
3372 {
3373 if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
3374 if event.type_() == atom!("compositionend") {
3375 let action = self
3376 .textinput
3377 .borrow_mut()
3378 .handle_compositionend(compositionevent);
3379 self.handle_key_reaction(action, event, can_gc);
3380 self.upcast::<Node>().dirty(NodeDamage::Other);
3381 self.update_placeholder_shown_state();
3382 } else if event.type_() == atom!("compositionupdate") {
3383 let action = self
3384 .textinput
3385 .borrow_mut()
3386 .handle_compositionupdate(compositionevent);
3387 self.handle_key_reaction(action, event, can_gc);
3388 self.upcast::<Node>().dirty(NodeDamage::Other);
3389 self.update_placeholder_shown_state();
3390 } else if event.type_() == atom!("compositionstart") {
3391 self.update_placeholder_shown_state();
3393 }
3394 event.mark_as_handled();
3395 }
3396 } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
3397 let reaction = self
3398 .textinput
3399 .borrow_mut()
3400 .handle_clipboard_event(clipboard_event);
3401 let flags = reaction.flags;
3402 if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
3403 self.owner_document().event_handler().fire_clipboard_event(
3404 None,
3405 ClipboardEventType::Change,
3406 can_gc,
3407 );
3408 }
3409 if flags.contains(ClipboardEventFlags::QueueInputEvent) {
3410 self.textinput.borrow().queue_input_event(
3411 self.upcast(),
3412 reaction.text,
3413 IsComposing::NotComposing,
3414 reaction.input_type,
3415 );
3416 }
3417 if !flags.is_empty() {
3418 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
3419 }
3420 } else if let Some(event) = event.downcast::<FocusEvent>() {
3421 self.handle_focus_event(event)
3422 }
3423
3424 self.value_changed(can_gc);
3425 }
3426
3427 fn cloning_steps(
3429 &self,
3430 copy: &Node,
3431 maybe_doc: Option<&Document>,
3432 clone_children: CloneChildrenFlag,
3433 can_gc: CanGc,
3434 ) {
3435 if let Some(s) = self.super_type() {
3436 s.cloning_steps(copy, maybe_doc, clone_children, can_gc);
3437 }
3438 let elem = copy.downcast::<HTMLInputElement>().unwrap();
3439 elem.value_dirty.set(self.value_dirty.get());
3440 elem.checked_changed.set(self.checked_changed.get());
3441 elem.upcast::<Element>()
3442 .set_state(ElementState::CHECKED, self.Checked());
3443 elem.textinput
3444 .borrow_mut()
3445 .set_content(self.textinput.borrow().get_content());
3446 self.value_changed(can_gc);
3447 }
3448}
3449
3450impl FormControl for HTMLInputElement {
3451 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
3452 self.form_owner.get()
3453 }
3454
3455 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
3456 self.form_owner.set(form);
3457 }
3458
3459 fn to_element(&self) -> &Element {
3460 self.upcast::<Element>()
3461 }
3462}
3463
3464impl Validatable for HTMLInputElement {
3465 fn as_element(&self) -> &Element {
3466 self.upcast()
3467 }
3468
3469 fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
3470 self.validity_state
3471 .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
3472 }
3473
3474 fn is_instance_validatable(&self) -> bool {
3475 match self.input_type() {
3482 InputType::Hidden | InputType::Button | InputType::Reset => false,
3483 _ => {
3484 !(self.upcast::<Element>().disabled_state() ||
3485 self.ReadOnly() ||
3486 is_barred_by_datalist_ancestor(self.upcast()))
3487 },
3488 }
3489 }
3490
3491 fn perform_validation(
3492 &self,
3493 validate_flags: ValidationFlags,
3494 can_gc: CanGc,
3495 ) -> ValidationFlags {
3496 let mut failed_flags = ValidationFlags::empty();
3497 let value = self.Value();
3498
3499 if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
3500 self.suffers_from_being_missing(&value)
3501 {
3502 failed_flags.insert(ValidationFlags::VALUE_MISSING);
3503 }
3504
3505 if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
3506 self.suffers_from_type_mismatch(&value)
3507 {
3508 failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
3509 }
3510
3511 if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
3512 self.suffers_from_pattern_mismatch(&value, can_gc)
3513 {
3514 failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
3515 }
3516
3517 if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
3518 self.suffers_from_bad_input(&value)
3519 {
3520 failed_flags.insert(ValidationFlags::BAD_INPUT);
3521 }
3522
3523 if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
3524 failed_flags |= self.suffers_from_length_issues(&value);
3525 }
3526
3527 if validate_flags.intersects(
3528 ValidationFlags::RANGE_UNDERFLOW |
3529 ValidationFlags::RANGE_OVERFLOW |
3530 ValidationFlags::STEP_MISMATCH,
3531 ) {
3532 failed_flags |= self.suffers_from_range_issues(&value);
3533 }
3534
3535 failed_flags & validate_flags
3536 }
3537}
3538
3539impl Activatable for HTMLInputElement {
3540 fn as_element(&self) -> &Element {
3541 self.upcast()
3542 }
3543
3544 fn is_instance_activatable(&self) -> bool {
3545 match self.input_type() {
3546 InputType::Submit |
3553 InputType::Reset |
3554 InputType::File |
3555 InputType::Image |
3556 InputType::Button => self.is_mutable(),
3557 InputType::Checkbox | InputType::Radio | InputType::Color => true,
3561 _ => false,
3562 }
3563 }
3564
3565 fn legacy_pre_activation_behavior(&self, can_gc: CanGc) -> Option<InputActivationState> {
3567 let ty = self.input_type();
3568 let activation_state = match ty {
3569 InputType::Checkbox => {
3570 let was_checked = self.Checked();
3571 let was_indeterminate = self.Indeterminate();
3572 self.SetIndeterminate(false);
3573 self.SetChecked(!was_checked, can_gc);
3574 Some(InputActivationState {
3575 checked: was_checked,
3576 indeterminate: was_indeterminate,
3577 checked_radio: None,
3578 old_type: InputType::Checkbox,
3579 })
3580 },
3581 InputType::Radio => {
3582 let root = self
3583 .upcast::<Node>()
3584 .GetRootNode(&GetRootNodeOptions::empty());
3585 let form_owner = self.form_owner();
3586 let checked_member = radio_group_iter(
3587 self,
3588 self.radio_group_name().as_ref(),
3589 form_owner.as_deref(),
3590 &root,
3591 )
3592 .find(|r| r.Checked());
3593 let was_checked = self.Checked();
3594 self.SetChecked(true, can_gc);
3595 Some(InputActivationState {
3596 checked: was_checked,
3597 indeterminate: false,
3598 checked_radio: checked_member.as_deref().map(DomRoot::from_ref),
3599 old_type: InputType::Radio,
3600 })
3601 },
3602 _ => None,
3603 };
3604
3605 if activation_state.is_some() {
3606 self.value_changed(can_gc);
3607 }
3608
3609 activation_state
3610 }
3611
3612 fn legacy_canceled_activation_behavior(
3614 &self,
3615 cache: Option<InputActivationState>,
3616 can_gc: CanGc,
3617 ) {
3618 let ty = self.input_type();
3620 let cache = match cache {
3621 Some(cache) => {
3622 if cache.old_type != ty {
3623 return;
3626 }
3627 cache
3628 },
3629 None => {
3630 return;
3631 },
3632 };
3633
3634 match ty {
3635 InputType::Checkbox => {
3637 self.SetIndeterminate(cache.indeterminate);
3638 self.SetChecked(cache.checked, can_gc);
3639 },
3640 InputType::Radio => {
3642 if let Some(ref o) = cache.checked_radio {
3643 let tree_root = self
3644 .upcast::<Node>()
3645 .GetRootNode(&GetRootNodeOptions::empty());
3646 if in_same_group(
3649 o,
3650 self.form_owner().as_deref(),
3651 self.radio_group_name().as_ref(),
3652 Some(&*tree_root),
3653 ) {
3654 o.SetChecked(true, can_gc);
3655 } else {
3656 self.SetChecked(false, can_gc);
3657 }
3658 } else {
3659 self.SetChecked(false, can_gc);
3660 }
3661 },
3662 _ => (),
3663 }
3664
3665 self.value_changed(can_gc);
3666 }
3667
3668 fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) {
3670 match self.input_type() {
3671 InputType::Submit | InputType::Image => {
3674 if let Some(form_owner) = self.form_owner() {
3676 let document = self.owner_document();
3678
3679 if !document.is_fully_active() {
3680 return;
3681 }
3682
3683 form_owner.submit(
3686 SubmittedFrom::NotFromForm,
3687 FormSubmitterElement::Input(self),
3688 can_gc,
3689 )
3690 }
3691 },
3692 InputType::Reset => {
3693 if let Some(form_owner) = self.form_owner() {
3696 let document = self.owner_document();
3697
3698 if !document.is_fully_active() {
3700 return;
3701 }
3702
3703 form_owner.reset(ResetFrom::NotFromForm, can_gc);
3705 }
3706 },
3707 InputType::Checkbox | InputType::Radio => {
3710 if !self.upcast::<Node>().is_connected() {
3712 return;
3713 }
3714
3715 let target = self.upcast::<EventTarget>();
3716
3717 target.fire_event_with_params(
3720 atom!("input"),
3721 EventBubbles::Bubbles,
3722 EventCancelable::NotCancelable,
3723 EventComposed::Composed,
3724 can_gc,
3725 );
3726
3727 target.fire_bubbling_event(atom!("change"), can_gc);
3730 },
3731 InputType::File => {
3733 self.select_files(None);
3734 },
3735 InputType::Color => {
3737 self.show_the_picker_if_applicable();
3738 },
3739 _ => (),
3740 }
3741 }
3742}
3743
3744fn filter_from_accept(s: &DOMString) -> Vec<FilterPattern> {
3746 let mut filter = vec![];
3747 for p in split_commas(&s.str()) {
3748 let p = p.trim();
3749 if let Some('.') = p.chars().next() {
3750 filter.push(FilterPattern(p[1..].to_string()));
3751 } else if let Some(exts) = mime_guess::get_mime_extensions_str(p) {
3752 for ext in exts {
3753 filter.push(FilterPattern(ext.to_string()));
3754 }
3755 }
3756 }
3757
3758 filter
3759}
3760
3761fn round_halves_positive(n: f64) -> f64 {
3762 if n.fract() == -0.5 {
3766 n.ceil()
3767 } else {
3768 n.round()
3769 }
3770}
3771
3772fn compile_pattern(
3776 cx: SafeJSContext,
3777 pattern_str: &str,
3778 out_regex: MutableHandleObject,
3779 can_gc: CanGc,
3780) -> bool {
3781 if check_js_regex_syntax(cx, pattern_str, can_gc) {
3783 let pattern_str = format!("^(?:{})$", pattern_str);
3785 let flags = RegExpFlags {
3786 flags_: RegExpFlag_UnicodeSets,
3787 };
3788 new_js_regex(cx, &pattern_str, flags, out_regex, can_gc)
3789 } else {
3790 false
3791 }
3792}
3793
3794#[expect(unsafe_code)]
3795fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str, _can_gc: CanGc) -> bool {
3798 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3799 unsafe {
3800 rooted!(in(*cx) let mut exception = UndefinedValue());
3801
3802 let valid = CheckRegExpSyntax(
3803 *cx,
3804 pattern.as_ptr(),
3805 pattern.len(),
3806 RegExpFlags {
3807 flags_: RegExpFlag_UnicodeSets,
3808 },
3809 exception.handle_mut(),
3810 );
3811
3812 if !valid {
3813 JS_ClearPendingException(*cx);
3814 return false;
3815 }
3816
3817 exception.is_undefined()
3820 }
3821}
3822
3823#[expect(unsafe_code)]
3824pub(crate) fn new_js_regex(
3825 cx: SafeJSContext,
3826 pattern: &str,
3827 flags: RegExpFlags,
3828 mut out_regex: MutableHandleObject,
3829 _can_gc: CanGc,
3830) -> bool {
3831 let pattern: Vec<u16> = pattern.encode_utf16().collect();
3832 unsafe {
3833 out_regex.set(NewUCRegExpObject(
3834 *cx,
3835 pattern.as_ptr(),
3836 pattern.len(),
3837 flags,
3838 ));
3839 if out_regex.is_null() {
3840 JS_ClearPendingException(*cx);
3841 return false;
3842 }
3843 }
3844 true
3845}
3846
3847#[expect(unsafe_code)]
3848fn matches_js_regex(
3849 cx: SafeJSContext,
3850 regex_obj: HandleObject,
3851 value: &str,
3852 _can_gc: CanGc,
3853) -> Result<bool, ()> {
3854 let mut value: Vec<u16> = value.encode_utf16().collect();
3855
3856 unsafe {
3857 let mut is_regex = false;
3858 assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
3859 assert!(is_regex);
3860
3861 rooted!(in(*cx) let mut rval = UndefinedValue());
3862 let mut index = 0;
3863
3864 let ok = ExecuteRegExpNoStatics(
3865 *cx,
3866 regex_obj,
3867 value.as_mut_ptr(),
3868 value.len(),
3869 &mut index,
3870 true,
3871 rval.handle_mut(),
3872 );
3873
3874 if ok {
3875 Ok(!rval.is_null())
3876 } else {
3877 JS_ClearPendingException(*cx);
3878 Err(())
3879 }
3880 }
3881}
3882
3883#[derive(MallocSizeOf)]
3887struct PendingWebDriverResponse {
3888 response_sender: GenericSender<Result<bool, ErrorStatus>>,
3890 expected_file_count: usize,
3892}
3893
3894impl PendingWebDriverResponse {
3895 fn finish(self, number_files_selected: usize) {
3896 if number_files_selected == self.expected_file_count {
3897 let _ = self.response_sender.send(Ok(false));
3898 } else {
3899 let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
3902 }
3903 }
3904}
3905
3906fn parse_color_value(value: &str, url: Url) -> AbsoluteColor {
3907 let urlextradata = url.into();
3910 let context = ParserContext::new(
3911 Origin::Author,
3912 &urlextradata,
3913 Some(CssRuleType::Style),
3914 ParsingMode::DEFAULT,
3915 QuirksMode::NoQuirks,
3916 Default::default(),
3917 None,
3918 None,
3919 );
3920 let mut input = ParserInput::new(value);
3921 let mut input = Parser::new(&mut input);
3922 Color::parse_and_compute(&context, &mut input, None)
3923 .map(|computed_color| computed_color.resolve_to_absolute(&AbsoluteColor::BLACK))
3924 .unwrap_or(AbsoluteColor::BLACK)
3925}