1use std::cell::{Cell, RefCell, RefMut};
6use std::{f64, ptr};
7
8use dom_struct::dom_struct;
9use embedder_traits::{EmbedderControlRequest, InputMethodRequest, RgbColor, SelectedFile};
10use encoding_rs::Encoding;
11use fonts::{ByteIndex, TextByteRange};
12use html5ever::{LocalName, Prefix, local_name};
13use js::context::JSContext;
14use js::jsapi::{ClippedTime, JSObject, RegExpFlag_UnicodeSets, RegExpFlags};
15use js::jsval::UndefinedValue;
16use js::rust::wrappers2::{
17 CheckRegExpSyntax, DateGetMsecSinceEpoch, ExecuteRegExpNoStatics, JS_ClearPendingException,
18 NewDateObject, NewUCRegExpObject, ObjectIsDate, ObjectIsRegExp,
19};
20use js::rust::{HandleObject, MutableHandleObject};
21use layout_api::{ScriptSelection, SharedSelection};
22use num_traits::ToPrimitive;
23use script_bindings::cell::{DomRefCell, Ref};
24use script_bindings::domstring::parse_floating_point_number;
25use servo_base::generic_channel::GenericSender;
26use servo_base::text::Utf16CodeUnitLength;
27use style::attr::AttrValue;
28use style::str::split_commas;
29use stylo_atoms::Atom;
30use stylo_dom::ElementState;
31use time::OffsetDateTime;
32use unicode_bidi::{BidiClass, bidi_class};
33use webdriver::error::ErrorStatus;
34
35use crate::clipboard_provider::EmbedderClipboardProvider;
36use crate::dom::activation::Activatable;
37use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
38use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
39use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
40use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
41use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
42use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
43use crate::dom::bindings::error::{Error, ErrorResult};
44use crate::dom::bindings::inheritance::Castable;
45use crate::dom::bindings::refcounted::Trusted;
46use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
47use crate::dom::bindings::str::{DOMString, USVString};
48use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType};
49use crate::dom::compositionevent::CompositionEvent;
50use crate::dom::document::Document;
51use crate::dom::document_embedder_controls::ControlElement;
52use crate::dom::element::attributes::storage::AttrRef;
53use crate::dom::element::{AttributeMutation, Element};
54use crate::dom::event::Event;
55use crate::dom::event::event::{EventBubbles, EventCancelable, EventComposed};
56use crate::dom::eventtarget::EventTarget;
57use crate::dom::filelist::FileList;
58use crate::dom::html::htmldatalistelement::HTMLDataListElement;
59use crate::dom::html::htmlelement::HTMLElement;
60use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
61use crate::dom::html::htmlformelement::{
62 FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, SubmittedFrom,
63};
64use crate::dom::htmlinputelement::radio_input_type::{
65 broadcast_radio_checked, perform_radio_group_validation,
66};
67use crate::dom::input_element::input_type::{InputActivationType, InputType};
68use crate::dom::iterators::ShadowIncluding;
69use crate::dom::keyboardevent::KeyboardEvent;
70use crate::dom::node::virtualmethods::VirtualMethods;
71use crate::dom::node::{
72 BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, UnbindContext,
73};
74use crate::dom::nodelist::NodeList;
75use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
76use crate::dom::types::{FocusEvent, MouseEvent};
77use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
78use crate::dom::validitystate::{ValidationFlags, ValidityState};
79use crate::realms::enter_auto_realm;
80use crate::textinput::{ClipboardEventFlags, IsComposing, KeyReaction, Lines, TextInput};
81
82pub(crate) mod button_input_type;
83pub(crate) mod checkbox_input_type;
84pub(crate) mod color_input_type;
85pub(crate) mod date_input_type;
86pub(crate) mod datetime_local_input_type;
87pub(crate) mod email_input_type;
88pub(crate) mod file_input_type;
89pub(crate) mod hidden_input_type;
90pub(crate) mod image_input_type;
91pub(crate) mod input_type;
92pub(crate) mod month_input_type;
93pub(crate) mod number_input_type;
94pub(crate) mod password_input_type;
95pub(crate) mod radio_input_type;
96pub(crate) mod range_input_type;
97pub(crate) mod reset_input_type;
98pub(crate) mod search_input_type;
99pub(crate) mod submit_input_type;
100pub(crate) mod tel_input_type;
101pub(crate) mod text_input_type;
102pub(crate) mod text_input_widget;
103pub(crate) mod text_value_widget;
104pub(crate) mod time_input_type;
105pub(crate) mod url_input_type;
106pub(crate) mod week_input_type;
107
108#[derive(Debug, PartialEq)]
109enum ValueMode {
110 Value,
112
113 Default,
115
116 DefaultOn,
118
119 Filename,
121}
122
123#[derive(Debug, PartialEq)]
124enum StepDirection {
125 Up,
126 Down,
127}
128
129#[dom_struct]
130pub(crate) struct HTMLInputElement {
131 htmlelement: HTMLElement,
132 input_type: DomRefCell<InputType>,
133
134 is_textual_or_password: Cell<bool>,
137
138 checked_changed: Cell<bool>,
140 placeholder: DomRefCell<DOMString>,
141 size: Cell<u32>,
142 maxlength: Cell<i32>,
143 minlength: Cell<i32>,
144 #[no_trace]
145 textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
146 value_dirty: Cell<bool>,
148 #[no_trace]
151 #[conditional_malloc_size_of]
152 shared_selection: SharedSelection,
153
154 form_owner: MutNullableDom<HTMLFormElement>,
155 labels_node_list: MutNullableDom<NodeList>,
156 validity_state: MutNullableDom<ValidityState>,
157 #[no_trace]
158 pending_webdriver_response: RefCell<Option<PendingWebDriverResponse>>,
159
160 has_scheduled_selectionchange_event: Cell<bool>,
162}
163
164#[derive(JSTraceable)]
165pub(crate) struct InputActivationState {
166 indeterminate: bool,
167 checked: bool,
168 checked_radio: Option<DomRoot<HTMLInputElement>>,
169 was_radio: bool,
170 was_checkbox: bool,
171 }
173
174static DEFAULT_INPUT_SIZE: u32 = 20;
175static DEFAULT_MAX_LENGTH: i32 = -1;
176static DEFAULT_MIN_LENGTH: i32 = -1;
177
178#[expect(non_snake_case)]
179impl HTMLInputElement {
180 fn new_inherited(
181 local_name: LocalName,
182 prefix: Option<Prefix>,
183 document: &Document,
184 ) -> HTMLInputElement {
185 let embedder_sender = document
186 .window()
187 .as_global_scope()
188 .script_to_embedder_chan()
189 .clone();
190 HTMLInputElement {
191 htmlelement: HTMLElement::new_inherited_with_state(
192 ElementState::ENABLED | ElementState::READWRITE,
193 local_name,
194 prefix,
195 document,
196 ),
197 input_type: DomRefCell::new(InputType::new_text()),
198 is_textual_or_password: Cell::new(true),
199 placeholder: DomRefCell::new(DOMString::new()),
200 checked_changed: Cell::new(false),
201 maxlength: Cell::new(DEFAULT_MAX_LENGTH),
202 minlength: Cell::new(DEFAULT_MIN_LENGTH),
203 size: Cell::new(DEFAULT_INPUT_SIZE),
204 textinput: DomRefCell::new(TextInput::new(
205 Lines::Single,
206 DOMString::new(),
207 EmbedderClipboardProvider {
208 embedder_sender,
209 webview_id: document.webview_id(),
210 },
211 )),
212 value_dirty: Cell::new(false),
213 shared_selection: Default::default(),
214 form_owner: Default::default(),
215 labels_node_list: MutNullableDom::new(None),
216 validity_state: Default::default(),
217 pending_webdriver_response: Default::default(),
218 has_scheduled_selectionchange_event: Default::default(),
219 }
220 }
221
222 pub(crate) fn new(
223 cx: &mut JSContext,
224 local_name: LocalName,
225 prefix: Option<Prefix>,
226 document: &Document,
227 proto: Option<HandleObject>,
228 ) -> DomRoot<HTMLInputElement> {
229 Node::reflect_node_with_proto(
230 cx,
231 Box::new(HTMLInputElement::new_inherited(
232 local_name, prefix, document,
233 )),
234 document,
235 proto,
236 )
237 }
238
239 pub(crate) fn auto_directionality(&self) -> Option<String> {
240 match *self.input_type() {
241 InputType::Text(_) | InputType::Search(_) | InputType::Url(_) | InputType::Email(_) => {
242 let value: String = String::from(self.Value());
243 Some(HTMLInputElement::directionality_from_value(&value))
244 },
245 _ => None,
246 }
247 }
248
249 pub(crate) fn directionality_from_value(value: &str) -> String {
250 if HTMLInputElement::is_first_strong_character_rtl(value) {
251 "rtl".to_owned()
252 } else {
253 "ltr".to_owned()
254 }
255 }
256
257 fn is_first_strong_character_rtl(value: &str) -> bool {
258 for ch in value.chars() {
259 return match bidi_class(ch) {
260 BidiClass::L => false,
261 BidiClass::AL => true,
262 BidiClass::R => true,
263 _ => continue,
264 };
265 }
266 false
267 }
268
269 fn value_mode(&self) -> ValueMode {
272 match *self.input_type() {
273 InputType::Submit(_) |
274 InputType::Reset(_) |
275 InputType::Button(_) |
276 InputType::Image(_) |
277 InputType::Hidden(_) => ValueMode::Default,
278
279 InputType::Checkbox(_) | InputType::Radio(_) => ValueMode::DefaultOn,
280
281 InputType::Color(_) |
282 InputType::Date(_) |
283 InputType::DatetimeLocal(_) |
284 InputType::Email(_) |
285 InputType::Month(_) |
286 InputType::Number(_) |
287 InputType::Password(_) |
288 InputType::Range(_) |
289 InputType::Search(_) |
290 InputType::Tel(_) |
291 InputType::Text(_) |
292 InputType::Time(_) |
293 InputType::Url(_) |
294 InputType::Week(_) => ValueMode::Value,
295
296 InputType::File(_) => ValueMode::Filename,
297 }
298 }
299
300 #[inline]
301 pub(crate) fn input_type(&self) -> Ref<'_, InputType> {
302 self.input_type.borrow()
303 }
304
305 pub(crate) fn is_nontypeable(&self) -> bool {
307 matches!(
308 *self.input_type(),
309 InputType::Button(_) |
310 InputType::Checkbox(_) |
311 InputType::Color(_) |
312 InputType::File(_) |
313 InputType::Hidden(_) |
314 InputType::Image(_) |
315 InputType::Radio(_) |
316 InputType::Range(_) |
317 InputType::Reset(_) |
318 InputType::Submit(_)
319 )
320 }
321
322 #[inline]
323 pub(crate) fn is_submit_button(&self) -> bool {
324 matches!(
325 *self.input_type(),
326 InputType::Submit(_) | InputType::Image(_)
327 )
328 }
329
330 pub(crate) fn is_auto_directionality_form_associated_element(&self) -> bool {
332 matches!(
333 *self.input_type(),
334 InputType::Hidden(_) |
335 InputType::Text(_) |
336 InputType::Search(_) |
337 InputType::Tel(_) |
338 InputType::Url(_) |
339 InputType::Email(_) |
340 InputType::Password(_) |
341 InputType::Submit(_) |
342 InputType::Reset(_) |
343 InputType::Button(_)
344 )
345 }
346
347 fn does_minmaxlength_apply(&self) -> bool {
348 matches!(
349 *self.input_type(),
350 InputType::Text(_) |
351 InputType::Search(_) |
352 InputType::Url(_) |
353 InputType::Tel(_) |
354 InputType::Email(_) |
355 InputType::Password(_)
356 )
357 }
358
359 fn does_pattern_apply(&self) -> bool {
360 matches!(
361 *self.input_type(),
362 InputType::Text(_) |
363 InputType::Search(_) |
364 InputType::Url(_) |
365 InputType::Tel(_) |
366 InputType::Email(_) |
367 InputType::Password(_)
368 )
369 }
370
371 fn does_multiple_apply(&self) -> bool {
372 matches!(*self.input_type(), InputType::Email(_))
373 }
374
375 fn does_value_as_number_apply(&self) -> bool {
378 matches!(
379 *self.input_type(),
380 InputType::Date(_) |
381 InputType::Month(_) |
382 InputType::Week(_) |
383 InputType::Time(_) |
384 InputType::DatetimeLocal(_) |
385 InputType::Number(_) |
386 InputType::Range(_)
387 )
388 }
389
390 fn does_value_as_date_apply(&self) -> bool {
391 matches!(
392 *self.input_type(),
393 InputType::Date(_) | InputType::Month(_) | InputType::Week(_) | InputType::Time(_)
394 )
395 }
396
397 fn allowed_value_step(&self) -> Option<f64> {
399 let default_step = self.default_step()?;
402
403 let Some(step_value) = self
406 .upcast::<Element>()
407 .get_attribute_string_value(&local_name!("step"))
408 else {
409 return Some(default_step * self.step_scale_factor());
410 };
411
412 if step_value.eq_ignore_ascii_case("any") {
415 return None;
416 }
417
418 let Some(parsed_value) =
422 parse_floating_point_number(&step_value).filter(|value| *value > 0.0)
423 else {
424 return Some(default_step * self.step_scale_factor());
425 };
426
427 Some(parsed_value * self.step_scale_factor())
431 }
432
433 fn minimum(&self) -> Option<f64> {
435 self.upcast::<Element>()
436 .get_attribute_string_value(&local_name!("min"))
437 .and_then(|value| self.convert_string_to_number(&value))
438 .or_else(|| self.default_minimum())
439 }
440
441 fn maximum(&self) -> Option<f64> {
443 self.upcast::<Element>()
444 .get_attribute_string_value(&local_name!("max"))
445 .and_then(|value| self.convert_string_to_number(&value))
446 .or_else(|| self.default_maximum())
447 }
448
449 fn stepped_minimum(&self) -> Option<f64> {
452 match (self.minimum(), self.allowed_value_step()) {
453 (Some(min), Some(allowed_step)) => {
454 let step_base = self.step_base();
455 let nsteps = (min - step_base) / allowed_step;
457 Some(step_base + (allowed_step * nsteps.ceil()))
459 },
460 (_, _) => None,
461 }
462 }
463
464 fn stepped_maximum(&self) -> Option<f64> {
467 match (self.maximum(), self.allowed_value_step()) {
468 (Some(max), Some(allowed_step)) => {
469 let step_base = self.step_base();
470 let nsteps = (max - step_base) / allowed_step;
472 Some(step_base + (allowed_step * nsteps.floor()))
474 },
475 (_, _) => None,
476 }
477 }
478
479 fn default_minimum(&self) -> Option<f64> {
481 match *self.input_type() {
482 InputType::Range(_) => Some(0.0),
483 _ => None,
484 }
485 }
486
487 fn default_maximum(&self) -> Option<f64> {
489 match *self.input_type() {
490 InputType::Range(_) => Some(100.0),
491 _ => None,
492 }
493 }
494
495 fn default_range_value(&self) -> f64 {
497 let min = self.minimum().unwrap_or(0.0);
498 let max = self.maximum().unwrap_or(100.0);
499 if max < min {
500 min
501 } else {
502 min + (max - min) * 0.5
503 }
504 }
505
506 fn default_step(&self) -> Option<f64> {
508 match *self.input_type() {
509 InputType::Date(_) => Some(1.0),
510 InputType::Month(_) => Some(1.0),
511 InputType::Week(_) => Some(1.0),
512 InputType::Time(_) => Some(60.0),
513 InputType::DatetimeLocal(_) => Some(60.0),
514 InputType::Number(_) => Some(1.0),
515 InputType::Range(_) => Some(1.0),
516 _ => None,
517 }
518 }
519
520 fn step_scale_factor(&self) -> f64 {
522 match *self.input_type() {
523 InputType::Date(_) => 86400000.0,
524 InputType::Month(_) => 1.0,
525 InputType::Week(_) => 604800000.0,
526 InputType::Time(_) => 1000.0,
527 InputType::DatetimeLocal(_) => 1000.0,
528 InputType::Number(_) => 1.0,
529 InputType::Range(_) => 1.0,
530 _ => unreachable!(),
531 }
532 }
533
534 fn step_base(&self) -> f64 {
536 if let Some(minimum) = self
540 .upcast::<Element>()
541 .get_attribute_string_value(&local_name!("min"))
542 .and_then(|value| self.convert_string_to_number(&value))
543 {
544 return minimum;
545 }
546
547 if let Some(value) = self
551 .upcast::<Element>()
552 .get_attribute_string_value(&local_name!("value"))
553 .and_then(|value| self.convert_string_to_number(&value))
554 {
555 return value;
556 }
557
558 if let Some(default_step_base) = self.default_step_base() {
560 return default_step_base;
561 }
562
563 0.0
565 }
566
567 fn default_step_base(&self) -> Option<f64> {
569 match *self.input_type() {
570 InputType::Week(_) => Some(-259200000.0),
571 _ => None,
572 }
573 }
574
575 fn step_up_or_down(&self, cx: &mut JSContext, n: i32, dir: StepDirection) -> ErrorResult {
579 if !self.does_value_as_number_apply() {
582 return Err(Error::InvalidState(None));
583 }
584 let step_base = self.step_base();
585
586 let Some(allowed_value_step) = self.allowed_value_step() else {
588 return Err(Error::InvalidState(None));
589 };
590
591 let minimum = self.minimum();
594 let maximum = self.maximum();
595 if let (Some(min), Some(max)) = (minimum, maximum) {
596 if min > max {
597 return Ok(());
598 }
599
600 if let Some(stepped_minimum) = self.stepped_minimum() &&
604 stepped_minimum > max
605 {
606 return Ok(());
607 }
608 }
609
610 let mut value: f64 = self
614 .convert_string_to_number(&self.Value().str())
615 .unwrap_or(0.0);
616
617 let valueBeforeStepping = value;
619
620 if (value - step_base) % allowed_value_step != 0.0 {
625 value = match dir {
626 StepDirection::Down =>
627 {
629 let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
630 intervals_from_base * allowed_value_step + step_base
631 },
632 StepDirection::Up =>
633 {
635 let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
636 intervals_from_base * allowed_value_step + step_base
637 },
638 };
639 }
640 else {
642 value += match dir {
647 StepDirection::Down => -f64::from(n) * allowed_value_step,
648 StepDirection::Up => f64::from(n) * allowed_value_step,
649 };
650 }
651
652 if let Some(min) = minimum &&
656 value < min
657 {
658 value = self.stepped_minimum().unwrap_or(value);
659 }
660
661 if let Some(max) = maximum &&
665 value > max
666 {
667 value = self.stepped_maximum().unwrap_or(value);
668 }
669
670 match dir {
674 StepDirection::Down => {
675 if value > valueBeforeStepping {
676 return Ok(());
677 }
678 },
679 StepDirection::Up => {
680 if value < valueBeforeStepping {
681 return Ok(());
682 }
683 },
684 }
685
686 self.SetValueAsNumber(cx, value)
690 }
691
692 fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
694 let list_string = self
695 .upcast::<Element>()
696 .get_string_attribute(&local_name!("list"));
697 if list_string.is_empty() {
698 return None;
699 }
700 let ancestor = self
701 .upcast::<Node>()
702 .GetRootNode(&GetRootNodeOptions::empty());
703 let first_with_id = &ancestor
704 .traverse_preorder(ShadowIncluding::No)
705 .find(|node| {
706 node.downcast::<Element>()
707 .is_some_and(|e| e.Id() == list_string)
708 });
709 first_with_id
710 .as_ref()
711 .and_then(|el| el.downcast::<HTMLDataListElement>())
712 .map(DomRoot::from_ref)
713 }
714
715 fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
717 self.input_type()
718 .as_specific()
719 .suffers_from_being_missing(self, value)
720 }
721
722 fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
724 if value.is_empty() {
725 return false;
726 }
727
728 self.input_type()
729 .as_specific()
730 .suffers_from_type_mismatch(self, value)
731 }
732
733 fn suffers_from_pattern_mismatch(&self, cx: &mut JSContext, value: &DOMString) -> bool {
735 let pattern_str = self.Pattern();
738 if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
739 return false;
740 }
741
742 let mut realm = enter_auto_realm(cx, self);
743 let cx = &mut realm;
744
745 rooted!(&in(cx) let mut pattern = ptr::null_mut::<JSObject>());
747 if compile_pattern(cx, &pattern_str.str(), pattern.handle_mut()) {
748 if self.Multiple() && self.does_multiple_apply() {
749 !split_commas(&value.str())
750 .all(|s| matches_js_regex(cx, pattern.handle(), s).unwrap_or(true))
751 } else {
752 !matches_js_regex(cx, pattern.handle(), &value.str()).unwrap_or(true)
753 }
754 } else {
755 false
757 }
758 }
759
760 fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
762 if value.is_empty() {
763 return false;
764 }
765
766 self.input_type()
767 .as_specific()
768 .suffers_from_bad_input(value)
769 }
770
771 fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
774 let value_dirty = self.value_dirty.get();
777 let textinput = self.textinput.borrow();
778 let edit_by_user = !textinput.was_last_change_by_set_content();
779
780 if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
781 return ValidationFlags::empty();
782 }
783
784 let mut failed_flags = ValidationFlags::empty();
785 let Utf16CodeUnitLength(value_len) = textinput.len_utf16();
786 let min_length = self.MinLength();
787 let max_length = self.MaxLength();
788
789 if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
790 failed_flags.insert(ValidationFlags::TOO_SHORT);
791 }
792
793 if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
794 failed_flags.insert(ValidationFlags::TOO_LONG);
795 }
796
797 failed_flags
798 }
799
800 fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
804 if value.is_empty() || !self.does_value_as_number_apply() {
805 return ValidationFlags::empty();
806 }
807
808 let Some(value_as_number) = self.convert_string_to_number(&value.str()) else {
809 return ValidationFlags::empty();
810 };
811
812 let mut failed_flags = ValidationFlags::empty();
813 let min_value = self.minimum();
814 let max_value = self.maximum();
815
816 let has_reversed_range = match (min_value, max_value) {
818 (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
819 _ => false,
820 };
821
822 if has_reversed_range {
823 if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
825 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
826 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
827 }
828 } else {
829 if let Some(min_value) = min_value &&
831 value_as_number < min_value
832 {
833 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
834 }
835 if let Some(max_value) = max_value &&
837 value_as_number > max_value
838 {
839 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
840 }
841 }
842
843 if let Some(step) = self.allowed_value_step() {
845 let diff = (self.step_base() - value_as_number) % step / value_as_number;
849 if diff.abs() > 1e-12 {
850 failed_flags.insert(ValidationFlags::STEP_MISMATCH);
851 }
852 }
853
854 failed_flags
855 }
856
857 pub(crate) fn is_textual_or_password(&self) -> bool {
859 self.is_textual_or_password.get()
860 }
861
862 fn may_have_embedder_control(&self) -> bool {
863 let el = self.upcast::<Element>();
864 matches!(*self.input_type(), InputType::Color(_)) && !el.disabled_state()
865 }
866
867 fn handle_key_reaction(&self, cx: &mut JSContext, action: KeyReaction, event: &Event) {
868 match action {
869 KeyReaction::TriggerDefaultAction => {
870 self.implicit_submission(cx);
871 event.mark_as_handled();
872 },
873 KeyReaction::DispatchInput(text, is_composing, input_type) => {
874 if event.IsTrusted() {
875 self.textinput.borrow().queue_input_event(
876 self.upcast(),
877 text,
878 is_composing,
879 input_type,
880 );
881 }
882 self.value_dirty.set(true);
883 self.update_placeholder_shown_state();
884 self.upcast::<Node>().dirty(NodeDamage::Other);
885 event.mark_as_handled();
886 },
887 KeyReaction::RedrawSelection => {
888 self.maybe_update_shared_selection();
889 event.mark_as_handled();
890 },
891 KeyReaction::Nothing => (),
892 }
893 }
894
895 fn value_for_shadow_dom(&self) -> DOMString {
897 let input_type = &*self.input_type();
898 match input_type {
899 InputType::Checkbox(_) |
900 InputType::Radio(_) |
901 InputType::Image(_) |
902 InputType::Hidden(_) |
903 InputType::Range(_) => input_type.as_specific().value_for_shadow_dom(self),
904 _ => {
905 if let Some(attribute_value) = self
906 .upcast::<Element>()
907 .get_attribute_string_value(&local_name!("value"))
908 {
909 return attribute_value.into();
910 }
911 input_type.as_specific().value_for_shadow_dom(self)
912 },
913 }
914 }
915
916 fn textinput_mut(&self) -> RefMut<'_, TextInput<EmbedderClipboardProvider>> {
917 self.textinput.borrow_mut()
918 }
919
920 fn schedule_a_selection_change_event(&self) {
922 if self.has_scheduled_selectionchange_event.get() {
924 return;
925 }
926 self.has_scheduled_selectionchange_event.set(true);
928 let this = Trusted::new(self);
930 self.owner_global()
931 .task_manager()
932 .user_interaction_task_source()
933 .queue(
934 task!(selectionchange_task_steps: move |cx| {
936 let this = this.root();
937 this.has_scheduled_selectionchange_event.set(false);
939 this.upcast::<EventTarget>().fire_event_with_params(
941 cx,
942 atom!("selectionchange"),
943 EventBubbles::Bubbles,
944 EventCancelable::NotCancelable,
945 EventComposed::Composed,
946 );
947 }),
952 );
953 }
954}
955
956impl<'dom> LayoutDom<'dom, HTMLInputElement> {
957 pub(crate) fn size_for_layout(self) -> u32 {
967 self.unsafe_get().size.get()
968 }
969
970 pub(crate) fn selection_for_layout(self) -> Option<SharedSelection> {
971 if !self.unsafe_get().is_textual_or_password.get() {
972 return None;
973 }
974 Some(self.unsafe_get().shared_selection.clone())
975 }
976}
977
978impl TextControlElement for HTMLInputElement {
979 fn selection_api_applies(&self) -> bool {
981 matches!(
982 *self.input_type(),
983 InputType::Text(_) |
984 InputType::Search(_) |
985 InputType::Url(_) |
986 InputType::Tel(_) |
987 InputType::Password(_)
988 )
989 }
990
991 fn has_selectable_text(&self) -> bool {
999 self.is_textual_or_password() && !self.textinput.borrow().get_content().is_empty()
1000 }
1001
1002 fn has_uncollapsed_selection(&self) -> bool {
1003 self.textinput.borrow().has_uncollapsed_selection()
1004 }
1005
1006 fn set_dirty_value_flag(&self, value: bool) {
1007 self.value_dirty.set(value)
1008 }
1009
1010 fn select_all(&self) {
1011 self.textinput.borrow_mut().select_all();
1012 self.maybe_update_shared_selection();
1013 }
1014
1015 fn maybe_update_shared_selection(&self) {
1016 let offsets = self.textinput.borrow().sorted_selection_offsets_range();
1017 let (start, end) = (offsets.start.0, offsets.end.0);
1018 let range = TextByteRange::new(ByteIndex(start), ByteIndex(end));
1019 let enabled = self.is_textual_or_password() && self.upcast::<Element>().focus_state();
1020
1021 let mut shared_selection = self.shared_selection.borrow_mut();
1022 let range_remained_equal = range == shared_selection.range;
1023 if range_remained_equal && enabled == shared_selection.enabled {
1024 return;
1025 }
1026
1027 if !range_remained_equal {
1028 self.schedule_a_selection_change_event();
1033 }
1034
1035 *shared_selection = ScriptSelection {
1036 range,
1037 character_range: self
1038 .textinput
1039 .borrow()
1040 .sorted_selection_character_offsets_range(),
1041 enabled,
1042 };
1043 self.owner_window().layout().set_needs_new_display_list();
1044 }
1045
1046 fn is_password_field(&self) -> bool {
1047 matches!(*self.input_type(), InputType::Password(_))
1048 }
1049
1050 fn placeholder_text<'a>(&'a self) -> Ref<'a, DOMString> {
1051 self.placeholder.borrow()
1052 }
1053
1054 fn value_text(&self) -> DOMString {
1055 self.Value()
1056 }
1057}
1058
1059impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1060 make_getter!(Accept, "accept");
1062
1063 make_setter!(SetAccept, "accept");
1065
1066 make_bool_getter!(Alpha, "alpha");
1068
1069 make_bool_setter!(SetAlpha, "alpha");
1071
1072 make_getter!(Alt, "alt");
1074
1075 make_setter!(SetAlt, "alt");
1077
1078 make_getter!(DirName, "dirname");
1080
1081 make_setter!(SetDirName, "dirname");
1083
1084 make_bool_getter!(Disabled, "disabled");
1086
1087 make_bool_setter!(SetDisabled, "disabled");
1089
1090 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1092 self.form_owner()
1093 }
1094
1095 fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1097 self.input_type()
1098 .as_specific()
1099 .get_files()
1100 .as_ref()
1101 .cloned()
1102 }
1103
1104 fn SetFiles(&self, _cx: &mut JSContext, files: Option<&FileList>) {
1106 if let Some(files) = files {
1107 self.input_type().as_specific().set_files(files)
1108 }
1109 }
1110
1111 make_bool_getter!(DefaultChecked, "checked");
1113
1114 make_bool_setter!(SetDefaultChecked, "checked");
1116
1117 fn Checked(&self) -> bool {
1119 self.upcast::<Element>()
1120 .state()
1121 .contains(ElementState::CHECKED)
1122 }
1123
1124 fn SetChecked(&self, cx: &mut JSContext, checked: bool) {
1126 self.update_checked_state(cx, checked, true);
1127 self.value_changed(cx);
1128 }
1129
1130 make_enumerated_getter!(
1132 ColorSpace,
1133 "colorspace",
1134 "limited-srgb" | "display-p3",
1135 missing => "limited-srgb",
1136 invalid => "limited-srgb"
1137 );
1138
1139 make_setter!(SetColorSpace, "colorspace");
1141
1142 make_bool_getter!(ReadOnly, "readonly");
1144
1145 make_bool_setter!(SetReadOnly, "readonly");
1147
1148 make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1150
1151 make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1153
1154 fn Type(&self) -> DOMString {
1156 DOMString::from(self.input_type().as_str())
1157 }
1158
1159 make_atomic_setter!(SetType, "type");
1161
1162 fn Value(&self) -> DOMString {
1164 match self.value_mode() {
1165 ValueMode::Value => self.textinput.borrow().get_content(),
1166 ValueMode::Default => self
1167 .upcast::<Element>()
1168 .get_attribute_string_value(&local_name!("value"))
1169 .map(|value| value.into())
1170 .unwrap_or_default(),
1171 ValueMode::DefaultOn => self
1172 .upcast::<Element>()
1173 .get_attribute_string_value(&local_name!("value"))
1174 .map(|value| value.into())
1175 .unwrap_or(DOMString::from("on")),
1176 ValueMode::Filename => {
1177 let mut path = DOMString::from("");
1178 match self.input_type().as_specific().get_files() {
1179 Some(ref fl) => match fl.Item(0) {
1180 Some(ref f) => {
1181 path.push_str("C:\\fakepath\\");
1182 path.push_str(&f.name().str());
1183 path
1184 },
1185 None => path,
1186 },
1187 None => path,
1188 }
1189 },
1190 }
1191 }
1192
1193 fn SetValue(&self, cx: &mut JSContext, mut value: DOMString) -> ErrorResult {
1195 match self.value_mode() {
1196 ValueMode::Value => {
1197 {
1198 self.value_dirty.set(true);
1200
1201 self.sanitize_value(&mut value);
1204
1205 let mut textinput = self.textinput.borrow_mut();
1206
1207 if textinput.get_content() != value {
1212 textinput.set_content(value);
1214
1215 textinput.clear_selection_to_end();
1216 }
1217 }
1218
1219 self.update_placeholder_shown_state();
1223 self.maybe_update_shared_selection();
1224 },
1225 ValueMode::Default | ValueMode::DefaultOn => {
1226 self.upcast::<Element>()
1227 .set_string_attribute(cx, &local_name!("value"), value);
1228 },
1229 ValueMode::Filename => {
1230 if value.is_empty() {
1231 let window = self.owner_window();
1232 let fl = FileList::new(cx, &window, vec![]);
1233 self.input_type().as_specific().set_files(&fl)
1234 } else {
1235 return Err(Error::InvalidState(None));
1236 }
1237 },
1238 }
1239
1240 self.value_changed(cx);
1241 self.upcast::<Node>().dirty(NodeDamage::Other);
1242 Ok(())
1243 }
1244
1245 make_getter!(DefaultValue, "value");
1247
1248 make_setter!(SetDefaultValue, "value");
1250
1251 make_getter!(Min, "min");
1253
1254 make_setter!(SetMin, "min");
1256
1257 fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1259 self.suggestions_source_element()
1260 }
1261
1262 #[expect(unsafe_code)]
1264 fn GetValueAsDate(&self, cx: &mut JSContext, mut return_value: MutableHandleObject) {
1265 if let Some(date_time) = self
1266 .input_type()
1267 .as_specific()
1268 .convert_string_to_naive_datetime(self.Value())
1269 {
1270 let time = ClippedTime {
1271 t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1272 };
1273 return_value.set(unsafe { NewDateObject(cx, time) });
1274 }
1275 }
1276
1277 #[expect(unsafe_code)]
1279 fn SetValueAsDate(&self, cx: &mut JSContext, value: *mut JSObject) -> ErrorResult {
1280 rooted!(&in(cx) let value = value);
1281 if !self.does_value_as_date_apply() {
1282 return Err(Error::InvalidState(None));
1283 }
1284 if value.is_null() {
1285 return self.SetValue(cx, DOMString::from(""));
1286 }
1287 let mut msecs: f64 = 0.0;
1288 unsafe {
1292 let mut is_date = false;
1293 if !ObjectIsDate(cx, value.handle(), &mut is_date) {
1294 return Err(Error::JSFailed);
1295 }
1296 if !is_date {
1297 return Err(Error::Type(c"Value was not a date".to_owned()));
1298 }
1299 if !DateGetMsecSinceEpoch(cx, value.handle(), &mut msecs) {
1300 return Err(Error::JSFailed);
1301 }
1302 if !msecs.is_finite() {
1303 return self.SetValue(cx, DOMString::from(""));
1304 }
1305 }
1306
1307 let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1308 return self.SetValue(cx, DOMString::from(""));
1309 };
1310 self.SetValue(
1311 cx,
1312 self.input_type()
1313 .as_specific()
1314 .convert_datetime_to_dom_string(date_time),
1315 )
1316 }
1317
1318 fn ValueAsNumber(&self) -> f64 {
1320 self.convert_string_to_number(&self.Value().str())
1321 .unwrap_or(f64::NAN)
1322 }
1323
1324 fn SetValueAsNumber(&self, cx: &mut JSContext, value: f64) -> ErrorResult {
1326 if value.is_infinite() {
1327 Err(Error::Type(c"value is not finite".to_owned()))
1328 } else if !self.does_value_as_number_apply() {
1329 Err(Error::InvalidState(None))
1330 } else if value.is_nan() {
1331 self.SetValue(cx, DOMString::from(""))
1332 } else if let Some(converted) = self.convert_number_to_string(value) {
1333 self.SetValue(cx, converted)
1334 } else {
1335 self.SetValue(cx, DOMString::from(""))
1340 }
1341 }
1342
1343 make_getter!(Name, "name");
1345
1346 make_atomic_setter!(SetName, "name");
1348
1349 make_getter!(Placeholder, "placeholder");
1351
1352 make_setter!(SetPlaceholder, "placeholder");
1354
1355 make_form_action_getter!(FormAction, "formaction");
1357
1358 make_setter!(SetFormAction, "formaction");
1360
1361 make_enumerated_getter!(
1363 FormEnctype,
1364 "formenctype",
1365 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1366 invalid => "application/x-www-form-urlencoded"
1367 );
1368
1369 make_setter!(SetFormEnctype, "formenctype");
1371
1372 make_enumerated_getter!(
1374 FormMethod,
1375 "formmethod",
1376 "get" | "post" | "dialog",
1377 invalid => "get"
1378 );
1379
1380 make_setter!(SetFormMethod, "formmethod");
1382
1383 make_getter!(FormTarget, "formtarget");
1385
1386 make_setter!(SetFormTarget, "formtarget");
1388
1389 make_bool_getter!(FormNoValidate, "formnovalidate");
1391
1392 make_bool_setter!(SetFormNoValidate, "formnovalidate");
1394
1395 make_getter!(Max, "max");
1397
1398 make_setter!(SetMax, "max");
1400
1401 make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1403
1404 make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1406
1407 make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1409
1410 make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1412
1413 make_bool_getter!(Multiple, "multiple");
1415
1416 make_bool_setter!(SetMultiple, "multiple");
1418
1419 make_getter!(Pattern, "pattern");
1421
1422 make_setter!(SetPattern, "pattern");
1424
1425 make_bool_getter!(Required, "required");
1427
1428 make_bool_setter!(SetRequired, "required");
1430
1431 make_url_getter!(Src, "src");
1433
1434 make_url_setter!(SetSrc, "src");
1436
1437 make_getter!(Step, "step");
1439
1440 make_setter!(SetStep, "step");
1442
1443 make_getter!(UseMap, "usemap");
1445
1446 make_setter!(SetUseMap, "usemap");
1448
1449 fn Indeterminate(&self) -> bool {
1451 self.upcast::<Element>()
1452 .state()
1453 .contains(ElementState::INDETERMINATE)
1454 }
1455
1456 fn SetIndeterminate(&self, _cx: &mut JSContext, val: bool) {
1458 self.upcast::<Element>()
1459 .set_state(ElementState::INDETERMINATE, val)
1460 }
1461
1462 fn GetLabels(&self, cx: &mut JSContext) -> Option<DomRoot<NodeList>> {
1466 if matches!(*self.input_type(), InputType::Hidden(_)) {
1467 None
1468 } else {
1469 Some(self.labels_node_list.or_init(|| {
1470 NodeList::new_labels_list(
1471 cx,
1472 self.upcast::<Node>().owner_doc().window(),
1473 self.upcast::<HTMLElement>(),
1474 )
1475 }))
1476 }
1477 }
1478
1479 fn Select(&self) {
1481 self.selection().dom_select();
1482 }
1483
1484 fn GetSelectionStart(&self) -> Option<u32> {
1486 self.selection().dom_start().map(|start| start.0 as u32)
1487 }
1488
1489 fn SetSelectionStart(&self, _cx: &mut JSContext, start: Option<u32>) -> ErrorResult {
1491 self.selection()
1492 .set_dom_start(start.map(Utf16CodeUnitLength::from))
1493 }
1494
1495 fn GetSelectionEnd(&self) -> Option<u32> {
1497 self.selection().dom_end().map(|end| end.0 as u32)
1498 }
1499
1500 fn SetSelectionEnd(&self, _cx: &mut JSContext, end: Option<u32>) -> ErrorResult {
1502 self.selection()
1503 .set_dom_end(end.map(Utf16CodeUnitLength::from))
1504 }
1505
1506 fn GetSelectionDirection(&self) -> Option<DOMString> {
1508 self.selection().dom_direction()
1509 }
1510
1511 fn SetSelectionDirection(
1513 &self,
1514 _cx: &mut JSContext,
1515 direction: Option<DOMString>,
1516 ) -> ErrorResult {
1517 self.selection().set_dom_direction(direction)
1518 }
1519
1520 fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
1522 self.selection().set_dom_range(
1523 Utf16CodeUnitLength::from(start),
1524 Utf16CodeUnitLength::from(end),
1525 direction,
1526 )
1527 }
1528
1529 fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
1531 self.selection()
1532 .set_dom_range_text(replacement, None, None, Default::default())
1533 }
1534
1535 fn SetRangeText_(
1537 &self,
1538 replacement: DOMString,
1539 start: u32,
1540 end: u32,
1541 selection_mode: SelectionMode,
1542 ) -> ErrorResult {
1543 self.selection().set_dom_range_text(
1544 replacement,
1545 Some(Utf16CodeUnitLength::from(start)),
1546 Some(Utf16CodeUnitLength::from(end)),
1547 selection_mode,
1548 )
1549 }
1550
1551 fn SelectFiles(&self, paths: Vec<DOMString>) {
1554 self.input_type()
1555 .as_specific()
1556 .select_files(self, Some(paths));
1557 }
1558
1559 fn StepUp(&self, cx: &mut JSContext, n: i32) -> ErrorResult {
1561 self.step_up_or_down(cx, n, StepDirection::Up)
1562 }
1563
1564 fn StepDown(&self, cx: &mut JSContext, n: i32) -> ErrorResult {
1566 self.step_up_or_down(cx, n, StepDirection::Down)
1567 }
1568
1569 fn WillValidate(&self) -> bool {
1571 self.is_instance_validatable()
1572 }
1573
1574 fn Validity(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
1576 self.validity_state(cx)
1577 }
1578
1579 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
1581 self.check_validity(cx)
1582 }
1583
1584 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
1586 self.report_validity(cx)
1587 }
1588
1589 fn ValidationMessage(&self, cx: &mut JSContext) -> DOMString {
1591 self.validation_message(cx)
1592 }
1593
1594 fn SetCustomValidity(&self, cx: &mut JSContext, error: DOMString) {
1596 self.validity_state(cx).set_custom_error_message(cx, error);
1597 }
1598}
1599
1600impl HTMLInputElement {
1601 pub(crate) fn form_datums(
1604 &self,
1605 submitter: Option<FormSubmitterElement>,
1606 encoding: Option<&'static Encoding>,
1607 ) -> (Vec<FormDatum>, bool) {
1608 let ty = self.Type();
1609 let name = self.Name();
1610 let is_submitter = match submitter {
1611 Some(FormSubmitterElement::Input(s)) => self == s,
1612 _ => false,
1613 };
1614
1615 match *self.input_type() {
1617 InputType::Submit(_) | InputType::Button(_) | InputType::Reset(_) if !is_submitter => {
1619 return (vec![], true);
1620 },
1621
1622 InputType::Radio(_) | InputType::Checkbox(_) if !self.Checked() => {
1624 return (vec![], true);
1625 },
1626
1627 InputType::Image(_) => return (vec![], true), _ => {
1632 if name.is_empty() {
1633 return (vec![], true);
1634 }
1635 },
1636 }
1637
1638 let datums = match *self.input_type() {
1639 InputType::Checkbox(_) | InputType::Radio(_) => {
1641 let field_value = self.Value();
1643 let value = if field_value.is_empty() {
1644 DOMString::from("on")
1645 } else {
1646 field_value
1647 };
1648 vec![FormDatum {
1650 ty,
1651 name,
1652 value: FormDatumValue::String(value),
1653 }]
1654 },
1655
1656 InputType::File(_) => {
1658 let mut datums = vec![];
1659
1660 let name = self.Name();
1662
1663 match self.GetFiles() {
1664 None => {
1666 datums.push(FormDatum {
1667 ty,
1670 name,
1671 value: FormDatumValue::String(DOMString::from("")),
1672 })
1673 },
1674 Some(fl) => {
1676 for f in fl.iter_files() {
1677 datums.push(FormDatum {
1678 ty: ty.clone(),
1679 name: name.clone(),
1680 value: FormDatumValue::File(DomRoot::from_ref(f)),
1681 });
1682 }
1683 },
1684 }
1685
1686 datums
1687 },
1688
1689 InputType::Hidden(_) if name.to_ascii_lowercase() == "_charset_" => {
1691 let charset = match encoding {
1693 None => DOMString::from("UTF-8"),
1694 Some(enc) => DOMString::from(enc.name()),
1695 };
1696 vec![FormDatum {
1698 ty,
1699 name,
1700 value: FormDatumValue::String(charset),
1701 }]
1702 },
1703
1704 _ => vec![FormDatum {
1706 ty,
1707 name,
1708 value: FormDatumValue::String(self.Value()),
1709 }],
1710 };
1711 (datums, false)
1712 }
1713
1714 fn radio_group_name(&self) -> Option<Atom> {
1716 self.upcast::<Element>()
1717 .get_name()
1718 .filter(|name| !name.is_empty())
1719 }
1720
1721 fn update_checked_state(&self, cx: &mut JSContext, checked: bool, dirty: bool) {
1722 self.upcast::<Element>()
1723 .set_state(ElementState::CHECKED, checked);
1724
1725 if dirty {
1726 self.checked_changed.set(true);
1727 }
1728
1729 if matches!(*self.input_type(), InputType::Radio(_)) && checked {
1730 broadcast_radio_checked(cx, self, self.radio_group_name().as_ref());
1731 }
1732
1733 self.upcast::<Node>().dirty(NodeDamage::Other);
1734 }
1735
1736 pub(crate) fn is_mutable(&self) -> bool {
1738 !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
1741 }
1742
1743 pub(crate) fn reset(&self, cx: &mut JSContext) {
1753 self.value_dirty.set(false);
1754
1755 let mut value = self.DefaultValue();
1757 self.sanitize_value(&mut value);
1758 self.textinput.borrow_mut().set_content(value);
1759
1760 let input_type = &*self.input_type();
1761 if matches!(input_type, InputType::Radio(_) | InputType::Checkbox(_)) {
1762 self.update_checked_state(cx, self.DefaultChecked(), false);
1763 self.checked_changed.set(false);
1764 }
1765
1766 if matches!(input_type, InputType::File(_)) {
1767 input_type
1768 .as_specific()
1769 .set_files(&FileList::new(cx, &self.owner_window(), vec![]));
1770 }
1771
1772 self.value_changed(cx);
1773 }
1774
1775 pub(crate) fn clear(&self, cx: &mut JSContext) {
1778 self.value_dirty.set(false);
1780 self.checked_changed.set(false);
1781 self.textinput.borrow_mut().set_content(DOMString::from(""));
1783 self.update_checked_state(cx, self.DefaultChecked(), false);
1785 if self.input_type().as_specific().get_files().is_some() {
1787 let window = self.owner_window();
1788 let filelist = FileList::new(cx, &window, vec![]);
1789 self.input_type().as_specific().set_files(&filelist);
1790 }
1791
1792 {
1795 let mut textinput = self.textinput.borrow_mut();
1796 let mut value = textinput.get_content();
1797 self.sanitize_value(&mut value);
1798 textinput.set_content(value);
1799 }
1800
1801 self.value_changed(cx);
1802 }
1803
1804 fn update_placeholder_shown_state(&self) {
1805 if !self.input_type().is_textual_or_password() {
1806 self.upcast::<Element>().set_placeholder_shown_state(false);
1807 } else {
1808 let has_placeholder = !self.placeholder.borrow().is_empty();
1809 let has_value = !self.textinput.borrow().is_empty();
1810 self.upcast::<Element>()
1811 .set_placeholder_shown_state(has_placeholder && !has_value);
1812 }
1813 }
1814
1815 pub(crate) fn select_files_for_webdriver(
1816 &self,
1817 test_paths: Vec<DOMString>,
1818 response_sender: GenericSender<Result<bool, ErrorStatus>>,
1819 ) {
1820 let mut stored_sender = self.pending_webdriver_response.borrow_mut();
1821 assert!(stored_sender.is_none());
1822
1823 *stored_sender = Some(PendingWebDriverResponse {
1824 response_sender,
1825 expected_file_count: test_paths.len(),
1826 });
1827
1828 self.input_type()
1829 .as_specific()
1830 .select_files(self, Some(test_paths));
1831 }
1832
1833 fn sanitize_value(&self, value: &mut DOMString) {
1835 self.input_type().as_specific().sanitize_value(self, value);
1836 }
1837
1838 fn selection(&self) -> TextControlSelection<'_, Self> {
1839 TextControlSelection::new(self, &self.textinput)
1840 }
1841
1842 fn implicit_submission(&self, cx: &mut JSContext) {
1844 let doc = self.owner_document();
1845 let node = doc.upcast::<Node>();
1846 let owner = self.form_owner();
1847 let form = match owner {
1848 None => return,
1849 Some(ref f) => f,
1850 };
1851
1852 if self.upcast::<Element>().click_in_progress() {
1853 return;
1854 }
1855 let submit_button = node
1856 .traverse_preorder(ShadowIncluding::No)
1857 .filter_map(DomRoot::downcast::<HTMLInputElement>)
1858 .filter(|input| matches!(*input.input_type(), InputType::Submit(_)))
1859 .find(|r| r.form_owner() == owner);
1860 match submit_button {
1861 Some(ref button) => {
1862 if button.is_instance_activatable() {
1863 button
1866 .upcast::<Node>()
1867 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
1868 }
1869 },
1870 None => {
1871 let mut inputs = node
1872 .traverse_preorder(ShadowIncluding::No)
1873 .filter_map(DomRoot::downcast::<HTMLInputElement>)
1874 .filter(|input| {
1875 input.form_owner() == owner &&
1876 matches!(
1877 *input.input_type(),
1878 InputType::Text(_) |
1879 InputType::Search(_) |
1880 InputType::Url(_) |
1881 InputType::Tel(_) |
1882 InputType::Email(_) |
1883 InputType::Password(_) |
1884 InputType::Date(_) |
1885 InputType::Month(_) |
1886 InputType::Week(_) |
1887 InputType::Time(_) |
1888 InputType::DatetimeLocal(_) |
1889 InputType::Number(_)
1890 )
1891 });
1892
1893 if inputs.nth(1).is_some() {
1894 return;
1896 }
1897 form.submit(
1898 cx,
1899 SubmittedFrom::NotFromForm,
1900 FormSubmitterElement::Form(form),
1901 );
1902 },
1903 }
1904 }
1905
1906 fn convert_string_to_number(&self, value: &str) -> Option<f64> {
1908 self.input_type()
1909 .as_specific()
1910 .convert_string_to_number(value)
1911 }
1912
1913 fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
1915 self.input_type()
1916 .as_specific()
1917 .convert_number_to_string(value)
1918 }
1919
1920 fn update_related_validity_states(&self, cx: &mut JSContext) {
1921 match *self.input_type() {
1922 InputType::Radio(_) => {
1923 perform_radio_group_validation(cx, self, self.radio_group_name().as_ref())
1924 },
1925 _ => {
1926 self.validity_state(cx)
1927 .perform_validation_and_update(cx, ValidationFlags::all());
1928 },
1929 }
1930 }
1931
1932 fn value_changed(&self, cx: &mut JSContext) {
1933 self.maybe_update_shared_selection();
1934 self.update_related_validity_states(cx);
1935 self.input_type().as_specific().update_shadow_tree(cx, self);
1936 }
1937
1938 fn show_the_picker_if_applicable(&self) {
1940 if !self.is_mutable() {
1944 return;
1945 }
1946
1947 self.input_type()
1950 .as_specific()
1951 .show_the_picker_if_applicable(self);
1952 }
1953
1954 pub(crate) fn handle_color_picker_response(
1955 &self,
1956 cx: &mut JSContext,
1957 response: Option<RgbColor>,
1958 ) {
1959 if let InputType::Color(ref color_input_type) = *self.input_type() {
1960 color_input_type.handle_color_picker_response(cx, self, response)
1961 }
1962 }
1963
1964 pub(crate) fn handle_file_picker_response(
1965 &self,
1966 cx: &mut JSContext,
1967 response: Option<Vec<SelectedFile>>,
1968 ) {
1969 if let InputType::File(ref file_input_type) = *self.input_type() {
1970 file_input_type.handle_file_picker_response(cx, self, response)
1971 }
1972 }
1973
1974 fn handle_focus_event(&self, event: &FocusEvent) {
1975 let event_type = event.upcast::<Event>().type_();
1976 if *event_type == *"blur" {
1977 self.owner_document()
1978 .embedder_controls()
1979 .hide_embedder_control(self.upcast());
1980 } else if *event_type == *"focus" {
1981 let input_type = &*self.input_type();
1982 let Ok(input_method_type) = input_type.try_into() else {
1983 return;
1984 };
1985
1986 self.owner_document()
1987 .embedder_controls()
1988 .show_embedder_control(
1989 ControlElement::Ime(DomRoot::from_ref(self.upcast())),
1990 EmbedderControlRequest::InputMethod(InputMethodRequest {
1991 input_method_type,
1992 text: String::from(self.Value()),
1993 insertion_point: self.GetSelectionEnd(),
1994 multiline: false,
1995 allow_virtual_keyboard: self.owner_window().has_sticky_activation(),
1997 }),
1998 None,
1999 );
2000 }
2001 }
2002
2003 fn handle_mouse_event(&self, mouse_event: &MouseEvent) {
2004 if mouse_event.upcast::<Event>().DefaultPrevented() {
2005 return;
2006 }
2007
2008 if !self.input_type().is_textual_or_password() || self.textinput.borrow().is_empty() {
2011 return;
2012 }
2013 let node = self.upcast();
2014 if self
2015 .textinput
2016 .borrow_mut()
2017 .handle_mouse_event(node, mouse_event)
2018 {
2019 self.maybe_update_shared_selection();
2020 }
2021 }
2022}
2023
2024impl VirtualMethods for HTMLInputElement {
2025 fn super_type(&self) -> Option<&dyn VirtualMethods> {
2026 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
2027 }
2028
2029 fn attribute_mutated(
2030 &self,
2031 cx: &mut JSContext,
2032 attr: AttrRef<'_>,
2033 mutation: AttributeMutation,
2034 ) {
2035 let could_have_had_embedder_control = self.may_have_embedder_control();
2036
2037 self.super_type()
2038 .unwrap()
2039 .attribute_mutated(cx, attr, mutation);
2040
2041 match *attr.local_name() {
2042 local_name!("disabled") => {
2043 let disabled_state = match mutation {
2044 AttributeMutation::Set(None, _) => true,
2045 AttributeMutation::Set(Some(_), _) => {
2046 return;
2048 },
2049 AttributeMutation::Removed => false,
2050 };
2051 let el = self.upcast::<Element>();
2052 el.set_disabled_state(disabled_state);
2053 el.set_enabled_state(!disabled_state);
2054 el.check_ancestors_disabled_state_for_form_control();
2055
2056 if self.input_type().is_textual() {
2057 let read_write = !(self.ReadOnly() || el.disabled_state());
2058 el.set_read_write_state(read_write);
2059 }
2060 },
2061 local_name!("checked") if !self.checked_changed.get() => {
2062 let checked_state = match mutation {
2063 AttributeMutation::Set(None, _) => true,
2064 AttributeMutation::Set(Some(_), _) => {
2065 return;
2067 },
2068 AttributeMutation::Removed => false,
2069 };
2070 self.update_checked_state(cx, checked_state, false);
2071 },
2072 local_name!("size") => {
2073 let size = mutation.new_value(attr).map(|value| value.as_uint());
2074 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
2075 },
2076 local_name!("type") => {
2077 match mutation {
2078 AttributeMutation::Set(previous_value, _) => {
2079 if previous_value
2083 .is_some_and(|previous_value| **previous_value == **attr.value())
2084 {
2085 return;
2086 }
2087
2088 let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
2089 let previously_selectable = self.selection_api_applies();
2090
2091 *self.input_type.borrow_mut() =
2092 InputType::new_from_atom(attr.value().as_atom());
2093 self.is_textual_or_password
2094 .set(self.input_type().is_textual_or_password());
2095
2096 let element = self.upcast::<Element>();
2097 if self.input_type().is_textual() {
2098 let read_write = !(self.ReadOnly() || element.disabled_state());
2099 element.set_read_write_state(read_write);
2100 } else {
2101 element.set_read_write_state(false);
2102 }
2103
2104 let new_value_mode = self.value_mode();
2105 match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
2106 (&ValueMode::Value, false, ValueMode::Default) |
2108 (&ValueMode::Value, false, ValueMode::DefaultOn) => {
2109 self.SetValue(cx, old_idl_value)
2110 .expect("Failed to set input value on type change to a default ValueMode.");
2111 },
2112
2113 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
2115 self.SetValue(
2116 cx,
2117 self.upcast::<Element>()
2118 .get_attribute_string_value(&local_name!("value"))
2119 .unwrap_or_default()
2120 .into(),
2121 )
2122 .expect(
2123 "Failed to set input value on type change to ValueMode::Value.",
2124 );
2125 self.value_dirty.set(false);
2126 },
2127
2128 (_, _, ValueMode::Filename)
2130 if old_value_mode != ValueMode::Filename =>
2131 {
2132 self.SetValue(cx, DOMString::from(""))
2133 .expect("Failed to set input value on type change to ValueMode::Filename.");
2134 },
2135 _ => {},
2136 }
2137
2138 self.input_type().as_specific().signal_type_change(cx, self);
2140
2141 let mut textinput = self.textinput.borrow_mut();
2143 let mut value = textinput.get_content();
2144 self.sanitize_value(&mut value);
2145 textinput.set_content(value);
2146 self.upcast::<Node>().dirty(NodeDamage::Other);
2147
2148 if self.does_minmaxlength_apply() {
2150 textinput.set_min_length(
2151 self.MinLength().to_usize().map(Utf16CodeUnitLength),
2152 );
2153 textinput.set_max_length(
2154 self.MaxLength().to_usize().map(Utf16CodeUnitLength),
2155 );
2156 } else {
2157 textinput.set_min_length(None);
2158 textinput.set_max_length(None);
2159 }
2160
2161 if !previously_selectable && self.selection_api_applies() {
2163 textinput.clear_selection_to_start();
2164 }
2165 },
2166 AttributeMutation::Removed => {
2167 self.input_type().as_specific().signal_type_change(cx, self);
2168 *self.input_type.borrow_mut() = InputType::new_text();
2169 self.is_textual_or_password
2170 .set(self.input_type().is_textual_or_password());
2171
2172 let element = self.upcast::<Element>();
2173 let read_write = !(self.ReadOnly() || element.disabled_state());
2174 element.set_read_write_state(read_write);
2175 },
2176 }
2177
2178 self.update_placeholder_shown_state();
2179 self.input_type()
2180 .as_specific()
2181 .update_placeholder_contents(cx, self);
2182 },
2183 local_name!("value") if !self.value_dirty.get() => {
2184 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
2188 let mut value = value.map_or(DOMString::new(), DOMString::from);
2189
2190 self.sanitize_value(&mut value);
2191 self.textinput.borrow_mut().set_content(value);
2192 self.update_placeholder_shown_state();
2193 },
2194 local_name!("maxlength") if self.does_minmaxlength_apply() => match *attr.value() {
2195 AttrValue::Int(_, value) => {
2196 let mut textinput = self.textinput.borrow_mut();
2197
2198 if value < 0 {
2199 textinput.set_max_length(None);
2200 } else {
2201 textinput.set_max_length(Some(Utf16CodeUnitLength(value as usize)))
2202 }
2203 },
2204 _ => panic!("Expected an AttrValue::Int"),
2205 },
2206 local_name!("minlength") if self.does_minmaxlength_apply() => match *attr.value() {
2207 AttrValue::Int(_, value) => {
2208 let mut textinput = self.textinput.borrow_mut();
2209
2210 if value < 0 {
2211 textinput.set_min_length(None);
2212 } else {
2213 textinput.set_min_length(Some(Utf16CodeUnitLength(value as usize)))
2214 }
2215 },
2216 _ => panic!("Expected an AttrValue::Int"),
2217 },
2218 local_name!("placeholder") => {
2219 {
2220 let mut placeholder = self.placeholder.borrow_mut();
2221 placeholder.clear();
2222 if let AttributeMutation::Set(..) = mutation {
2223 placeholder
2224 .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
2225 }
2226 }
2227 self.update_placeholder_shown_state();
2228 self.input_type()
2229 .as_specific()
2230 .update_placeholder_contents(cx, self);
2231 },
2232 local_name!("readonly") => {
2233 if self.input_type().is_textual() {
2234 let el = self.upcast::<Element>();
2235 match mutation {
2236 AttributeMutation::Set(..) => {
2237 el.set_read_write_state(false);
2238 },
2239 AttributeMutation::Removed => {
2240 el.set_read_write_state(!el.disabled_state());
2241 },
2242 }
2243 }
2244 },
2245 local_name!("form") => {
2246 self.form_attribute_mutated(cx, mutation);
2247 },
2248 _ => {
2249 self.input_type()
2250 .as_specific()
2251 .attribute_mutated(cx, self, attr, mutation);
2252 },
2253 }
2254
2255 self.value_changed(cx);
2256
2257 if could_have_had_embedder_control && !self.may_have_embedder_control() {
2258 self.owner_document()
2259 .embedder_controls()
2260 .hide_embedder_control(self.upcast());
2261 }
2262 }
2263
2264 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
2265 match *name {
2266 local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
2267 local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
2268 local_name!("type") => AttrValue::from_atomic(value.into()),
2269 local_name!("maxlength") => {
2270 AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
2271 },
2272 local_name!("minlength") => {
2273 AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
2274 },
2275 _ => self
2276 .super_type()
2277 .unwrap()
2278 .parse_plain_attribute(name, value),
2279 }
2280 }
2281
2282 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
2283 if let Some(s) = self.super_type() {
2284 s.bind_to_tree(cx, context);
2285 }
2286 self.upcast::<Element>()
2287 .check_ancestors_disabled_state_for_form_control();
2288
2289 self.input_type()
2290 .as_specific()
2291 .bind_to_tree(cx, self, context);
2292
2293 self.value_changed(cx);
2294 }
2295
2296 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
2297 let form_owner = self.form_owner();
2298 self.super_type().unwrap().unbind_from_tree(cx, context);
2299
2300 let node = self.upcast::<Node>();
2301 let el = self.upcast::<Element>();
2302 if node
2303 .ancestors()
2304 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
2305 {
2306 el.check_ancestors_disabled_state_for_form_control();
2307 } else {
2308 el.check_disabled_attribute();
2309 }
2310
2311 self.input_type()
2312 .as_specific()
2313 .unbind_from_tree(cx, self, form_owner, context);
2314
2315 self.validity_state(cx)
2316 .perform_validation_and_update(cx, ValidationFlags::all());
2317 }
2318
2319 fn handle_event(&self, cx: &mut JSContext, event: &Event) {
2325 if let Some(mouse_event) = event.downcast::<MouseEvent>() {
2326 self.handle_mouse_event(mouse_event);
2327 event.mark_as_handled();
2328 } else if event.type_() == atom!("keydown") &&
2329 !event.DefaultPrevented() &&
2330 self.input_type().is_textual_or_password()
2331 {
2332 if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
2333 let action = self.textinput.borrow_mut().handle_keydown(keyevent);
2336 self.handle_key_reaction(cx, action, event);
2337 }
2338 } else if (event.type_() == atom!("compositionstart") ||
2339 event.type_() == atom!("compositionupdate") ||
2340 event.type_() == atom!("compositionend")) &&
2341 self.input_type().is_textual_or_password()
2342 {
2343 if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
2344 if event.type_() == atom!("compositionend") {
2345 let action = self
2346 .textinput
2347 .borrow_mut()
2348 .handle_compositionend(compositionevent);
2349 self.handle_key_reaction(cx, action, event);
2350 self.upcast::<Node>().dirty(NodeDamage::Other);
2351 self.update_placeholder_shown_state();
2352 } else if event.type_() == atom!("compositionupdate") {
2353 let action = self
2354 .textinput
2355 .borrow_mut()
2356 .handle_compositionupdate(compositionevent);
2357 self.handle_key_reaction(cx, action, event);
2358 self.upcast::<Node>().dirty(NodeDamage::Other);
2359 self.update_placeholder_shown_state();
2360 } else if event.type_() == atom!("compositionstart") {
2361 self.update_placeholder_shown_state();
2363 }
2364 event.mark_as_handled();
2365 }
2366 } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
2367 let reaction = self
2368 .textinput
2369 .borrow_mut()
2370 .handle_clipboard_event(clipboard_event);
2371 let flags = reaction.flags;
2372 if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
2373 self.owner_document().event_handler().fire_clipboard_event(
2374 cx,
2375 None,
2376 ClipboardEventType::Change,
2377 );
2378 }
2379 if flags.contains(ClipboardEventFlags::QueueInputEvent) {
2380 self.textinput.borrow().queue_input_event(
2381 self.upcast(),
2382 reaction.text,
2383 IsComposing::NotComposing,
2384 reaction.input_type,
2385 );
2386 }
2387 if !flags.is_empty() {
2388 event.mark_as_handled();
2389 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
2390 }
2391 } else if let Some(event) = event.downcast::<FocusEvent>() {
2392 self.handle_focus_event(event)
2393 }
2394
2395 self.value_changed(cx);
2396
2397 if let Some(super_type) = self.super_type() {
2398 super_type.handle_event(cx, event);
2399 }
2400 }
2401
2402 fn cloning_steps(
2404 &self,
2405 cx: &mut JSContext,
2406 copy: &Node,
2407 maybe_doc: Option<&Document>,
2408 clone_children: CloneChildrenFlag,
2409 ) {
2410 if let Some(s) = self.super_type() {
2411 s.cloning_steps(cx, copy, maybe_doc, clone_children);
2412 }
2413 let elem = copy.downcast::<HTMLInputElement>().unwrap();
2414 elem.value_dirty.set(self.value_dirty.get());
2415 elem.checked_changed.set(self.checked_changed.get());
2416 elem.upcast::<Element>()
2417 .set_state(ElementState::CHECKED, self.Checked());
2418 elem.upcast::<Element>()
2421 .set_state(ElementState::INDETERMINATE, self.Indeterminate());
2422 elem.textinput
2423 .borrow_mut()
2424 .set_content(self.textinput.borrow().get_content());
2425 self.value_changed(cx);
2426 }
2427}
2428
2429impl FormControl for HTMLInputElement {
2430 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
2431 self.form_owner.get()
2432 }
2433
2434 fn set_form_owner(&self, _cx: &mut JSContext, form: Option<&HTMLFormElement>) {
2435 self.form_owner.set(form);
2436 }
2437
2438 fn to_html_element(&self) -> &HTMLElement {
2439 self.upcast::<HTMLElement>()
2440 }
2441}
2442
2443impl Validatable for HTMLInputElement {
2444 fn as_element(&self) -> &Element {
2445 self.upcast()
2446 }
2447
2448 fn validity_state(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
2449 self.validity_state
2450 .or_init(|| ValidityState::new(cx, &self.owner_window(), self.upcast()))
2451 }
2452
2453 fn is_instance_validatable(&self) -> bool {
2454 match *self.input_type() {
2461 InputType::Hidden(_) | InputType::Button(_) | InputType::Reset(_) => false,
2462 _ => {
2463 !(self.upcast::<Element>().disabled_state() ||
2464 self.ReadOnly() ||
2465 is_barred_by_datalist_ancestor(self.upcast()))
2466 },
2467 }
2468 }
2469
2470 fn perform_validation(
2471 &self,
2472 cx: &mut JSContext,
2473 validate_flags: ValidationFlags,
2474 ) -> ValidationFlags {
2475 let mut failed_flags = ValidationFlags::empty();
2476 let value = self.Value();
2477
2478 if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
2479 self.suffers_from_being_missing(&value)
2480 {
2481 failed_flags.insert(ValidationFlags::VALUE_MISSING);
2482 }
2483
2484 if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
2485 self.suffers_from_type_mismatch(&value)
2486 {
2487 failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
2488 }
2489
2490 if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
2491 self.suffers_from_pattern_mismatch(cx, &value)
2492 {
2493 failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
2494 }
2495
2496 if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
2497 self.suffers_from_bad_input(&value)
2498 {
2499 failed_flags.insert(ValidationFlags::BAD_INPUT);
2500 }
2501
2502 if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
2503 failed_flags |= self.suffers_from_length_issues(&value);
2504 }
2505
2506 if validate_flags.intersects(
2507 ValidationFlags::RANGE_UNDERFLOW |
2508 ValidationFlags::RANGE_OVERFLOW |
2509 ValidationFlags::STEP_MISMATCH,
2510 ) {
2511 failed_flags |= self.suffers_from_range_issues(&value);
2512 }
2513
2514 failed_flags & validate_flags
2515 }
2516}
2517
2518impl Activatable for HTMLInputElement {
2519 fn as_element(&self) -> &Element {
2520 self.upcast()
2521 }
2522
2523 fn is_instance_activatable(&self) -> bool {
2524 match *self.input_type() {
2525 InputType::Submit(_) |
2532 InputType::Reset(_) |
2533 InputType::File(_) |
2534 InputType::Image(_) |
2535 InputType::Button(_) => self.is_mutable(),
2536 InputType::Checkbox(_) | InputType::Radio(_) | InputType::Color(_) => true,
2540 _ => false,
2541 }
2542 }
2543
2544 fn legacy_pre_activation_behavior(&self, cx: &mut JSContext) -> Option<InputActivationState> {
2546 let activation_state = self
2547 .input_type()
2548 .as_specific()
2549 .legacy_pre_activation_behavior(cx, self);
2550
2551 if activation_state.is_some() {
2552 self.value_changed(cx);
2553 }
2554
2555 activation_state
2556 }
2557
2558 fn legacy_canceled_activation_behavior(
2560 &self,
2561 cx: &mut JSContext,
2562 cache: Option<InputActivationState>,
2563 ) {
2564 let ty = self.input_type();
2566 let cache = match cache {
2567 Some(cache) => {
2568 if (cache.was_radio && !matches!(*ty, InputType::Radio(_))) ||
2569 (cache.was_checkbox && !matches!(*ty, InputType::Checkbox(_)))
2570 {
2571 return;
2574 }
2575 cache
2576 },
2577 None => {
2578 return;
2579 },
2580 };
2581
2582 ty.as_specific()
2584 .legacy_canceled_activation_behavior(cx, self, cache);
2585
2586 self.value_changed(cx);
2587 }
2588
2589 fn activation_behavior(&self, cx: &mut JSContext, event: &Event, target: &EventTarget) {
2591 let input_activation_type = {
2592 let input_type = self.input_type();
2593 InputActivationType::new_from_input_type(&input_type)
2594 };
2595
2596 if let Some(input_activation_type) = input_activation_type {
2597 input_activation_type
2598 .as_specific()
2599 .activation_behavior(cx, self, event, target);
2600 }
2601 }
2602}
2603
2604fn compile_pattern(cx: &mut JSContext, pattern_str: &str, out_regex: MutableHandleObject) -> bool {
2608 if check_js_regex_syntax(cx, pattern_str) {
2610 let pattern_str = format!("^(?:{})$", pattern_str);
2612 let flags = RegExpFlags {
2613 flags_: RegExpFlag_UnicodeSets,
2614 };
2615 new_js_regex(cx, &pattern_str, flags, out_regex)
2616 } else {
2617 false
2618 }
2619}
2620
2621#[expect(unsafe_code)]
2622fn check_js_regex_syntax(cx: &mut JSContext, pattern: &str) -> bool {
2625 let pattern: Vec<u16> = pattern.encode_utf16().collect();
2626 rooted!(&in(cx) let mut exception = UndefinedValue());
2627
2628 let valid = unsafe {
2629 CheckRegExpSyntax(
2630 cx,
2631 pattern.as_ptr(),
2632 pattern.len(),
2633 RegExpFlags {
2634 flags_: RegExpFlag_UnicodeSets,
2635 },
2636 exception.handle_mut(),
2637 )
2638 };
2639
2640 if !valid {
2641 unsafe { JS_ClearPendingException(cx) };
2642 return false;
2643 }
2644
2645 exception.is_undefined()
2648}
2649
2650#[expect(unsafe_code)]
2651fn new_js_regex(
2652 cx: &mut JSContext,
2653 pattern: &str,
2654 flags: RegExpFlags,
2655 mut out_regex: MutableHandleObject,
2656) -> bool {
2657 let pattern: Vec<u16> = pattern.encode_utf16().collect();
2658 out_regex.set(unsafe { NewUCRegExpObject(cx, pattern.as_ptr(), pattern.len(), flags) });
2659
2660 if out_regex.is_null() {
2661 unsafe { JS_ClearPendingException(cx) };
2662 return false;
2663 }
2664 true
2665}
2666
2667#[expect(unsafe_code)]
2668fn matches_js_regex(cx: &mut JSContext, regex_obj: HandleObject, value: &str) -> Result<bool, ()> {
2669 let mut value: Vec<u16> = value.encode_utf16().collect();
2670
2671 let mut is_regex = false;
2672 assert!(unsafe { ObjectIsRegExp(cx, regex_obj, &mut is_regex) });
2673 assert!(is_regex);
2674
2675 rooted!(&in(cx) let mut rval = UndefinedValue());
2676 let mut index = 0;
2677
2678 let ok = unsafe {
2679 ExecuteRegExpNoStatics(
2680 cx,
2681 regex_obj,
2682 value.as_mut_ptr(),
2683 value.len(),
2684 &mut index,
2685 true,
2686 rval.handle_mut(),
2687 )
2688 };
2689
2690 if ok {
2691 Ok(!rval.is_null())
2692 } else {
2693 unsafe { JS_ClearPendingException(cx) };
2694 Err(())
2695 }
2696}
2697
2698#[derive(MallocSizeOf)]
2702struct PendingWebDriverResponse {
2703 response_sender: GenericSender<Result<bool, ErrorStatus>>,
2705 expected_file_count: usize,
2707}
2708
2709impl PendingWebDriverResponse {
2710 fn finish(self, number_files_selected: usize) {
2711 if number_files_selected == self.expected_file_count {
2712 let _ = self.response_sender.send(Ok(false));
2713 } else {
2714 let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
2717 }
2718 }
2719}