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