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 html5ever::{LocalName, Prefix, local_name};
12use js::context::JSContext;
13use js::jsapi::{ClippedTime, JSObject, RegExpFlag_UnicodeSets, RegExpFlags};
14use js::jsval::UndefinedValue;
15use js::rust::wrappers2::{
16 CheckRegExpSyntax, DateGetMsecSinceEpoch, ExecuteRegExpNoStatics, JS_ClearPendingException,
17 NewDateObject, NewUCRegExpObject, ObjectIsDate, ObjectIsRegExp,
18};
19use js::rust::{HandleObject, MutableHandleObject};
20use num_traits::ToPrimitive;
21use script_bindings::cell::{DomRefCell, Ref};
22use script_bindings::domstring::parse_floating_point_number;
23use servo_base::generic_channel::GenericSender;
24use servo_base::text::{RangeAny, Utf16CodeUnits, Utf32CodeUnits};
25use style::attr::AttrValue;
26use style::str::split_commas;
27use stylo_atoms::Atom;
28use stylo_dom::ElementState;
29use time::OffsetDateTime;
30use unicode_bidi::{BidiClass, bidi_class};
31use webdriver::error::ErrorStatus;
32
33use crate::dom::activation::Activatable;
34use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
35use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
36use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
37use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
38use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
39use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
40use crate::dom::bindings::error::{Error, ErrorResult};
41use crate::dom::bindings::inheritance::Castable;
42use crate::dom::bindings::refcounted::Trusted;
43use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
44use crate::dom::bindings::str::{DOMString, USVString};
45use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType};
46use crate::dom::compositionevent::CompositionEvent;
47use crate::dom::document::Document;
48use crate::dom::document_embedder_controls::ControlElement;
49use crate::dom::element::attributes::storage::AttrRef;
50use crate::dom::element::{AttributeMutation, Element};
51use crate::dom::event::Event;
52use crate::dom::event::event::{EventBubbles, EventCancelable, EventComposed};
53use crate::dom::eventtarget::EventTarget;
54use crate::dom::filelist::FileList;
55use crate::dom::html::form_controls::input_type::radio_input_type::{
56 broadcast_radio_checked, perform_radio_group_validation,
57};
58use crate::dom::html::form_controls::input_type::{InputActivationType, InputType};
59use crate::dom::html::form_controls::text_control::{TextControlElement, TextControlSelection};
60use crate::dom::html::form_controls::text_input::{
61 ClipboardEventFlags, EmbedderClipboardProvider, IsComposing, KeyReaction, Lines, TextInput,
62};
63use crate::dom::html::htmldatalistelement::HTMLDataListElement;
64use crate::dom::html::htmlelement::HTMLElement;
65use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
66use crate::dom::html::htmlformelement::{
67 FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, SubmittedFrom,
68};
69use crate::dom::inputevent::HitTestResult;
70use crate::dom::iterators::ShadowIncluding;
71use crate::dom::keyboardevent::KeyboardEvent;
72use crate::dom::node::virtualmethods::VirtualMethods;
73use crate::dom::node::{
74 BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, UnbindContext,
75};
76use crate::dom::nodelist::NodeList;
77use crate::dom::types::{FocusEvent, MouseEvent};
78use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
79use crate::dom::validitystate::{ValidationFlags, ValidityState};
80use crate::realms::enter_auto_realm;
81
82#[derive(Debug, PartialEq)]
83pub(crate) enum ValueMode {
84 Value,
86
87 Default,
89
90 DefaultOn,
92
93 Filename,
95}
96
97#[derive(Debug, PartialEq)]
98enum StepDirection {
99 Up,
100 Down,
101}
102
103#[dom_struct]
104pub(crate) struct HTMLInputElement {
105 htmlelement: HTMLElement,
106 input_type: DomRefCell<InputType>,
107
108 is_textual_or_password: Cell<bool>,
111
112 checked_changed: Cell<bool>,
114 placeholder: DomRefCell<DOMString>,
115 size: Cell<u32>,
116 maxlength: Cell<i32>,
117 minlength: Cell<i32>,
118 #[no_trace]
119 textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
120 value_dirty: Cell<bool>,
122 form_owner: MutNullableDom<HTMLFormElement>,
123 labels_node_list: MutNullableDom<NodeList>,
124 validity_state: MutNullableDom<ValidityState>,
125 #[no_trace]
126 pending_webdriver_response: RefCell<Option<PendingWebDriverResponse>>,
127
128 has_scheduled_selectionchange_event: Cell<bool>,
130}
131
132#[derive(JSTraceable)]
133pub(crate) struct InputActivationState {
134 pub(crate) indeterminate: bool,
135 pub(crate) checked: bool,
136 pub(crate) checked_radio: Option<DomRoot<HTMLInputElement>>,
137 pub(crate) was_radio: bool,
138 pub(crate) was_checkbox: bool,
139 }
141
142static DEFAULT_INPUT_SIZE: u32 = 20;
143static DEFAULT_MAX_LENGTH: i32 = -1;
144static DEFAULT_MIN_LENGTH: i32 = -1;
145
146#[expect(non_snake_case)]
147impl HTMLInputElement {
148 fn new_inherited(
149 local_name: LocalName,
150 prefix: Option<Prefix>,
151 document: &Document,
152 ) -> HTMLInputElement {
153 let embedder_sender = document
154 .window()
155 .as_global_scope()
156 .script_to_embedder_chan()
157 .clone();
158 HTMLInputElement {
159 htmlelement: HTMLElement::new_inherited_with_state(
160 ElementState::ENABLED | ElementState::READWRITE,
161 local_name,
162 prefix,
163 document,
164 ),
165 input_type: DomRefCell::new(InputType::new_text()),
166 is_textual_or_password: Cell::new(true),
167 placeholder: DomRefCell::new(DOMString::new()),
168 checked_changed: Cell::new(false),
169 maxlength: Cell::new(DEFAULT_MAX_LENGTH),
170 minlength: Cell::new(DEFAULT_MIN_LENGTH),
171 size: Cell::new(DEFAULT_INPUT_SIZE),
172 textinput: DomRefCell::new(TextInput::new(
173 Lines::Single,
174 DOMString::new(),
175 EmbedderClipboardProvider {
176 embedder_sender,
177 webview_id: document.webview_id(),
178 },
179 )),
180 value_dirty: Cell::new(false),
181 form_owner: Default::default(),
182 labels_node_list: MutNullableDom::new(None),
183 validity_state: Default::default(),
184 pending_webdriver_response: Default::default(),
185 has_scheduled_selectionchange_event: Default::default(),
186 }
187 }
188
189 pub(crate) fn new(
190 cx: &mut JSContext,
191 local_name: LocalName,
192 prefix: Option<Prefix>,
193 document: &Document,
194 proto: Option<HandleObject>,
195 ) -> DomRoot<HTMLInputElement> {
196 Node::reflect_node_with_proto(
197 cx,
198 Box::new(HTMLInputElement::new_inherited(
199 local_name, prefix, document,
200 )),
201 document,
202 proto,
203 )
204 }
205
206 pub(crate) fn auto_directionality(&self) -> Option<String> {
207 match *self.input_type() {
208 InputType::Text(_) | InputType::Search(_) | InputType::Url(_) | InputType::Email(_) => {
209 let value: String = String::from(self.Value());
210 Some(HTMLInputElement::directionality_from_value(&value))
211 },
212 _ => None,
213 }
214 }
215
216 pub(crate) fn directionality_from_value(value: &str) -> String {
217 if HTMLInputElement::is_first_strong_character_rtl(value) {
218 "rtl".to_owned()
219 } else {
220 "ltr".to_owned()
221 }
222 }
223
224 fn is_first_strong_character_rtl(value: &str) -> bool {
225 for ch in value.chars() {
226 return match bidi_class(ch) {
227 BidiClass::L => false,
228 BidiClass::AL => true,
229 BidiClass::R => true,
230 _ => continue,
231 };
232 }
233 false
234 }
235
236 pub(crate) fn value_mode(&self) -> ValueMode {
239 match *self.input_type() {
240 InputType::Submit(_) |
241 InputType::Reset(_) |
242 InputType::Button(_) |
243 InputType::Image(_) |
244 InputType::Hidden(_) => ValueMode::Default,
245
246 InputType::Checkbox(_) | InputType::Radio(_) => ValueMode::DefaultOn,
247
248 InputType::Color(_) |
249 InputType::Date(_) |
250 InputType::DatetimeLocal(_) |
251 InputType::Email(_) |
252 InputType::Month(_) |
253 InputType::Number(_) |
254 InputType::Password(_) |
255 InputType::Range(_) |
256 InputType::Search(_) |
257 InputType::Tel(_) |
258 InputType::Text(_) |
259 InputType::Time(_) |
260 InputType::Url(_) |
261 InputType::Week(_) => ValueMode::Value,
262
263 InputType::File(_) => ValueMode::Filename,
264 }
265 }
266
267 #[inline]
268 pub(crate) fn input_type(&self) -> Ref<'_, InputType> {
269 self.input_type.borrow()
270 }
271
272 pub(crate) fn is_nontypeable(&self) -> bool {
274 matches!(
275 *self.input_type(),
276 InputType::Button(_) |
277 InputType::Checkbox(_) |
278 InputType::Color(_) |
279 InputType::File(_) |
280 InputType::Hidden(_) |
281 InputType::Image(_) |
282 InputType::Radio(_) |
283 InputType::Range(_) |
284 InputType::Reset(_) |
285 InputType::Submit(_)
286 )
287 }
288
289 #[inline]
290 pub(crate) fn is_submit_button(&self) -> bool {
291 matches!(
292 *self.input_type(),
293 InputType::Submit(_) | InputType::Image(_)
294 )
295 }
296
297 pub(crate) fn is_auto_directionality_form_associated_element(&self) -> bool {
299 matches!(
300 *self.input_type(),
301 InputType::Hidden(_) |
302 InputType::Text(_) |
303 InputType::Search(_) |
304 InputType::Tel(_) |
305 InputType::Url(_) |
306 InputType::Email(_) |
307 InputType::Password(_) |
308 InputType::Submit(_) |
309 InputType::Reset(_) |
310 InputType::Button(_)
311 )
312 }
313
314 fn does_minmaxlength_apply(&self) -> bool {
315 matches!(
316 *self.input_type(),
317 InputType::Text(_) |
318 InputType::Search(_) |
319 InputType::Url(_) |
320 InputType::Tel(_) |
321 InputType::Email(_) |
322 InputType::Password(_)
323 )
324 }
325
326 fn does_pattern_apply(&self) -> bool {
327 matches!(
328 *self.input_type(),
329 InputType::Text(_) |
330 InputType::Search(_) |
331 InputType::Url(_) |
332 InputType::Tel(_) |
333 InputType::Email(_) |
334 InputType::Password(_)
335 )
336 }
337
338 fn does_multiple_apply(&self) -> bool {
339 matches!(*self.input_type(), InputType::Email(_))
340 }
341
342 fn does_value_as_number_apply(&self) -> bool {
345 matches!(
346 *self.input_type(),
347 InputType::Date(_) |
348 InputType::Month(_) |
349 InputType::Week(_) |
350 InputType::Time(_) |
351 InputType::DatetimeLocal(_) |
352 InputType::Number(_) |
353 InputType::Range(_)
354 )
355 }
356
357 fn does_value_as_date_apply(&self) -> bool {
358 matches!(
359 *self.input_type(),
360 InputType::Date(_) | InputType::Month(_) | InputType::Week(_) | InputType::Time(_)
361 )
362 }
363
364 pub(crate) fn allowed_value_step(&self) -> Option<f64> {
366 let default_step = self.default_step()?;
369
370 let Some(step_value) = self
373 .upcast::<Element>()
374 .get_attribute_string_value(&local_name!("step"))
375 else {
376 return Some(default_step * self.step_scale_factor());
377 };
378
379 if step_value.eq_ignore_ascii_case("any") {
382 return None;
383 }
384
385 let Some(parsed_value) =
389 parse_floating_point_number(&step_value).filter(|value| *value > 0.0)
390 else {
391 return Some(default_step * self.step_scale_factor());
392 };
393
394 Some(parsed_value * self.step_scale_factor())
398 }
399
400 pub(crate) fn minimum(&self) -> Option<f64> {
402 self.upcast::<Element>()
403 .get_attribute_string_value(&local_name!("min"))
404 .and_then(|value| self.convert_string_to_number(&value))
405 .or_else(|| self.default_minimum())
406 }
407
408 pub(crate) fn maximum(&self) -> Option<f64> {
410 self.upcast::<Element>()
411 .get_attribute_string_value(&local_name!("max"))
412 .and_then(|value| self.convert_string_to_number(&value))
413 .or_else(|| self.default_maximum())
414 }
415
416 pub(crate) fn stepped_minimum(&self) -> Option<f64> {
419 match (self.minimum(), self.allowed_value_step()) {
420 (Some(min), Some(allowed_step)) => {
421 let step_base = self.step_base();
422 let nsteps = (min - step_base) / allowed_step;
424 Some(step_base + (allowed_step * nsteps.ceil()))
426 },
427 (_, _) => None,
428 }
429 }
430
431 pub(crate) fn stepped_maximum(&self) -> Option<f64> {
434 match (self.maximum(), self.allowed_value_step()) {
435 (Some(max), Some(allowed_step)) => {
436 let step_base = self.step_base();
437 let nsteps = (max - step_base) / allowed_step;
439 Some(step_base + (allowed_step * nsteps.floor()))
441 },
442 (_, _) => None,
443 }
444 }
445
446 fn default_minimum(&self) -> Option<f64> {
448 match *self.input_type() {
449 InputType::Range(_) => Some(0.0),
450 _ => None,
451 }
452 }
453
454 fn default_maximum(&self) -> Option<f64> {
456 match *self.input_type() {
457 InputType::Range(_) => Some(100.0),
458 _ => None,
459 }
460 }
461
462 pub(crate) fn default_range_value(&self) -> f64 {
464 let min = self.minimum().unwrap_or(0.0);
465 let max = self.maximum().unwrap_or(100.0);
466 if max < min {
467 min
468 } else {
469 min + (max - min) * 0.5
470 }
471 }
472
473 fn default_step(&self) -> Option<f64> {
475 match *self.input_type() {
476 InputType::Date(_) => Some(1.0),
477 InputType::Month(_) => Some(1.0),
478 InputType::Week(_) => Some(1.0),
479 InputType::Time(_) => Some(60.0),
480 InputType::DatetimeLocal(_) => Some(60.0),
481 InputType::Number(_) => Some(1.0),
482 InputType::Range(_) => Some(1.0),
483 _ => None,
484 }
485 }
486
487 fn step_scale_factor(&self) -> f64 {
489 match *self.input_type() {
490 InputType::Date(_) => 86400000.0,
491 InputType::Month(_) => 1.0,
492 InputType::Week(_) => 604800000.0,
493 InputType::Time(_) => 1000.0,
494 InputType::DatetimeLocal(_) => 1000.0,
495 InputType::Number(_) => 1.0,
496 InputType::Range(_) => 1.0,
497 _ => unreachable!(),
498 }
499 }
500
501 pub(crate) fn step_base(&self) -> f64 {
503 if let Some(minimum) = self
507 .upcast::<Element>()
508 .get_attribute_string_value(&local_name!("min"))
509 .and_then(|value| self.convert_string_to_number(&value))
510 {
511 return minimum;
512 }
513
514 if let Some(value) = self
518 .upcast::<Element>()
519 .get_attribute_string_value(&local_name!("value"))
520 .and_then(|value| self.convert_string_to_number(&value))
521 {
522 return value;
523 }
524
525 if let Some(default_step_base) = self.default_step_base() {
527 return default_step_base;
528 }
529
530 0.0
532 }
533
534 fn default_step_base(&self) -> Option<f64> {
536 match *self.input_type() {
537 InputType::Week(_) => Some(-259200000.0),
538 _ => None,
539 }
540 }
541
542 fn step_up_or_down(&self, cx: &mut JSContext, n: i32, dir: StepDirection) -> ErrorResult {
546 if !self.does_value_as_number_apply() {
549 return Err(Error::InvalidState(Some(
550 "Input element does not implement `stepDown()` or `stepUp()`".into(),
551 )));
552 }
553 let step_base = self.step_base();
554
555 let Some(allowed_value_step) = self.allowed_value_step() else {
557 return Err(Error::InvalidState(Some(
558 "Input element does not have a value step".into(),
559 )));
560 };
561
562 let minimum = self.minimum();
565 let maximum = self.maximum();
566 if let (Some(min), Some(max)) = (minimum, maximum) {
567 if min > max {
568 return Ok(());
569 }
570
571 if let Some(stepped_minimum) = self.stepped_minimum() &&
575 stepped_minimum > max
576 {
577 return Ok(());
578 }
579 }
580
581 let mut value: f64 = self
585 .convert_string_to_number(&self.Value().str())
586 .unwrap_or(0.0);
587
588 let valueBeforeStepping = value;
590
591 if (value - step_base) % allowed_value_step != 0.0 {
596 value = match dir {
597 StepDirection::Down =>
598 {
600 let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
601 intervals_from_base * allowed_value_step + step_base
602 },
603 StepDirection::Up =>
604 {
606 let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
607 intervals_from_base * allowed_value_step + step_base
608 },
609 };
610 }
611 else {
613 value += match dir {
618 StepDirection::Down => -f64::from(n) * allowed_value_step,
619 StepDirection::Up => f64::from(n) * allowed_value_step,
620 };
621 }
622
623 if let Some(min) = minimum &&
627 value < min
628 {
629 value = self.stepped_minimum().unwrap_or(value);
630 }
631
632 if let Some(max) = maximum &&
636 value > max
637 {
638 value = self.stepped_maximum().unwrap_or(value);
639 }
640
641 match dir {
645 StepDirection::Down => {
646 if value > valueBeforeStepping {
647 return Ok(());
648 }
649 },
650 StepDirection::Up => {
651 if value < valueBeforeStepping {
652 return Ok(());
653 }
654 },
655 }
656
657 self.SetValueAsNumber(cx, value)
661 }
662
663 fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
665 let list_string = self
666 .upcast::<Element>()
667 .get_string_attribute(&local_name!("list"));
668 if list_string.is_empty() {
669 return None;
670 }
671 let ancestor = self
672 .upcast::<Node>()
673 .GetRootNode(&GetRootNodeOptions::empty());
674 let first_with_id = &ancestor
675 .traverse_preorder(ShadowIncluding::No)
676 .find(|node| {
677 node.downcast::<Element>()
678 .is_some_and(|e| e.Id() == list_string)
679 });
680 first_with_id
681 .as_ref()
682 .and_then(|el| el.downcast::<HTMLDataListElement>())
683 .map(DomRoot::from_ref)
684 }
685
686 fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
688 self.input_type()
689 .as_specific()
690 .suffers_from_being_missing(self, value)
691 }
692
693 fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
695 if value.is_empty() {
696 return false;
697 }
698
699 self.input_type()
700 .as_specific()
701 .suffers_from_type_mismatch(self, value)
702 }
703
704 fn suffers_from_pattern_mismatch(&self, cx: &mut JSContext, value: &DOMString) -> bool {
706 let pattern_str = self.Pattern();
709 if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
710 return false;
711 }
712
713 let mut realm = enter_auto_realm(cx, self);
714 let cx = &mut realm;
715
716 rooted!(&in(cx) let mut pattern = ptr::null_mut::<JSObject>());
718 if compile_pattern(cx, &pattern_str.str(), pattern.handle_mut()) {
719 if self.Multiple() && self.does_multiple_apply() {
720 !split_commas(&value.str())
721 .all(|s| matches_js_regex(cx, pattern.handle(), s).unwrap_or(true))
722 } else {
723 !matches_js_regex(cx, pattern.handle(), &value.str()).unwrap_or(true)
724 }
725 } else {
726 false
728 }
729 }
730
731 fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
733 if value.is_empty() {
734 return false;
735 }
736
737 self.input_type()
738 .as_specific()
739 .suffers_from_bad_input(value)
740 }
741
742 fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
745 let value_dirty = self.value_dirty.get();
748 let textinput = self.textinput.borrow();
749 let edit_by_user = !textinput.was_last_change_by_set_content();
750
751 if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
752 return ValidationFlags::empty();
753 }
754
755 let mut failed_flags = ValidationFlags::empty();
756 let Utf16CodeUnits(value_len) = textinput.len_utf16();
757 let min_length = self.MinLength();
758 let max_length = self.MaxLength();
759
760 if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
761 failed_flags.insert(ValidationFlags::TOO_SHORT);
762 }
763
764 if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
765 failed_flags.insert(ValidationFlags::TOO_LONG);
766 }
767
768 failed_flags
769 }
770
771 fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
775 if value.is_empty() || !self.does_value_as_number_apply() {
776 return ValidationFlags::empty();
777 }
778
779 let Some(value_as_number) = self.convert_string_to_number(&value.str()) else {
780 return ValidationFlags::empty();
781 };
782
783 let mut failed_flags = ValidationFlags::empty();
784 let min_value = self.minimum();
785 let max_value = self.maximum();
786
787 let has_reversed_range = match (min_value, max_value) {
789 (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
790 _ => false,
791 };
792
793 if has_reversed_range {
794 if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
796 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
797 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
798 }
799 } else {
800 if let Some(min_value) = min_value &&
802 value_as_number < min_value
803 {
804 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
805 }
806 if let Some(max_value) = max_value &&
808 value_as_number > max_value
809 {
810 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
811 }
812 }
813
814 if let Some(step) = self.allowed_value_step() {
816 let diff = (self.step_base() - value_as_number) % step / value_as_number;
820 if diff.abs() > 1e-12 {
821 failed_flags.insert(ValidationFlags::STEP_MISMATCH);
822 }
823 }
824
825 failed_flags
826 }
827
828 pub(crate) fn is_textual_or_password(&self) -> bool {
830 self.is_textual_or_password.get()
831 }
832
833 fn may_have_embedder_control(&self) -> bool {
834 let el = self.upcast::<Element>();
835 matches!(*self.input_type(), InputType::Color(_)) && !el.disabled_state()
836 }
837
838 fn handle_key_reaction(&self, cx: &mut JSContext, action: KeyReaction, event: &Event) {
839 match action {
840 KeyReaction::TriggerDefaultAction => {
841 self.implicit_submission(cx);
842 event.mark_as_handled();
843 },
844 KeyReaction::DispatchInput(text, is_composing, input_type) => {
845 if event.IsTrusted() {
846 self.textinput.borrow().queue_input_event(
847 self.upcast(),
848 text,
849 is_composing,
850 input_type,
851 );
852 }
853 self.value_dirty.set(true);
854 self.update_placeholder_shown_state();
855 self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
856 event.mark_as_handled();
857 },
858 KeyReaction::RedrawSelection => {
859 self.maybe_update_shared_selection();
860 event.mark_as_handled();
861 },
862 KeyReaction::Nothing => (),
863 }
864 }
865
866 pub(crate) fn value_for_shadow_dom(&self) -> DOMString {
868 let input_type = &*self.input_type();
869 match input_type {
870 InputType::Checkbox(_) |
871 InputType::Radio(_) |
872 InputType::Image(_) |
873 InputType::Hidden(_) |
874 InputType::Range(_) => input_type.as_specific().value_for_shadow_dom(self),
875 _ => {
876 if let Some(attribute_value) = self
877 .upcast::<Element>()
878 .get_attribute_string_value(&local_name!("value"))
879 {
880 return attribute_value.into();
881 }
882 input_type.as_specific().value_for_shadow_dom(self)
883 },
884 }
885 }
886
887 pub(crate) fn textinput_mut(&self) -> RefMut<'_, TextInput<EmbedderClipboardProvider>> {
888 self.textinput.borrow_mut()
889 }
890
891 fn schedule_a_selection_change_event(&self) {
893 if self.has_scheduled_selectionchange_event.get() {
895 return;
896 }
897 self.has_scheduled_selectionchange_event.set(true);
899 let this = Trusted::new(self);
901 self.owner_global()
902 .task_manager()
903 .user_interaction_task_source()
904 .queue(
905 task!(selectionchange_task_steps: move |cx| {
907 let this = this.root();
908 this.has_scheduled_selectionchange_event.set(false);
910 this.upcast::<EventTarget>().fire_event_with_params(
912 cx,
913 atom!("selectionchange"),
914 EventBubbles::Bubbles,
915 EventCancelable::NotCancelable,
916 EventComposed::Composed,
917 );
918 }),
923 );
924 }
925}
926
927impl<'dom> LayoutDom<'dom, HTMLInputElement> {
928 pub(crate) fn size_for_layout(self) -> u32 {
938 self.unsafe_get().size.get()
939 }
940
941 pub(crate) fn selection_for_layout(self) -> Option<RangeAny<Utf32CodeUnits>> {
942 let element = self.unsafe_get();
943 if !element.is_textual_or_password.get() {
944 return None;
945 }
946 #[expect(unsafe_code)]
947 let textinput = unsafe { element.textinput.borrow_for_layout() };
948 textinput.selection_for_layout
949 }
950}
951
952impl TextControlElement for HTMLInputElement {
953 fn selection_api_applies(&self) -> bool {
955 matches!(
956 *self.input_type(),
957 InputType::Text(_) |
958 InputType::Search(_) |
959 InputType::Url(_) |
960 InputType::Tel(_) |
961 InputType::Password(_)
962 )
963 }
964
965 fn has_selectable_text(&self) -> bool {
973 self.is_textual_or_password() && !self.textinput.borrow().get_content().is_empty()
974 }
975
976 fn has_uncollapsed_selection(&self) -> bool {
977 self.textinput.borrow().has_uncollapsed_selection()
978 }
979
980 fn set_dirty_value_flag(&self, value: bool) {
981 self.value_dirty.set(value)
982 }
983
984 fn select_all(&self) {
985 self.textinput.borrow_mut().select_all();
986 self.maybe_update_shared_selection();
987 }
988
989 fn maybe_update_shared_selection(&self) {
990 let mut text_input = self.textinput.borrow_mut();
991 let selection_range = text_input.selection_start()..text_input.selection_end();
992 let enabled = self.is_textual_or_password() && self.upcast::<Element>().focus_state();
993
994 let range_remained_equal = selection_range == text_input.previous_selection_range;
995 if range_remained_equal && enabled == text_input.selection_for_layout.is_some() {
996 return;
997 }
998
999 if !range_remained_equal {
1000 self.schedule_a_selection_change_event();
1005 }
1006
1007 let selection = enabled.then(|| text_input.sorted_selection_character_offsets_range());
1008 text_input.previous_selection_range = selection_range;
1009 text_input.selection_for_layout = selection;
1010
1011 if let Some(text_input_widget) = self.input_type.borrow().as_specific().text_input_widget()
1012 {
1013 if text_input_widget.borrow().set_text_run_selection(selection) {
1014 self.owner_window().layout().set_needs_new_display_list();
1016 } else {
1017 }
1019 } else {
1020 }
1022 }
1023
1024 fn is_password_field(&self) -> bool {
1025 matches!(*self.input_type(), InputType::Password(_))
1026 }
1027
1028 fn placeholder_text<'a>(&'a self) -> Ref<'a, DOMString> {
1029 self.placeholder.borrow()
1030 }
1031
1032 fn value_text(&self) -> DOMString {
1033 self.Value()
1034 }
1035}
1036
1037impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1038 make_getter!(Accept, "accept");
1040
1041 make_setter!(SetAccept, "accept");
1043
1044 make_bool_getter!(Alpha, "alpha");
1046
1047 make_bool_setter!(SetAlpha, "alpha");
1049
1050 make_getter!(Alt, "alt");
1052
1053 make_setter!(SetAlt, "alt");
1055
1056 make_getter!(DirName, "dirname");
1058
1059 make_setter!(SetDirName, "dirname");
1061
1062 make_bool_getter!(Disabled, "disabled");
1064
1065 make_bool_setter!(SetDisabled, "disabled");
1067
1068 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1070 self.form_owner()
1071 }
1072
1073 fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1075 self.input_type()
1076 .as_specific()
1077 .get_files()
1078 .as_ref()
1079 .cloned()
1080 }
1081
1082 fn SetFiles(&self, _cx: &mut JSContext, files: Option<&FileList>) {
1084 if let Some(files) = files {
1085 self.input_type().as_specific().set_files(files)
1086 }
1087 }
1088
1089 make_bool_getter!(DefaultChecked, "checked");
1091
1092 make_bool_setter!(SetDefaultChecked, "checked");
1094
1095 fn Checked(&self) -> bool {
1097 self.upcast::<Element>()
1098 .state()
1099 .contains(ElementState::CHECKED)
1100 }
1101
1102 fn SetChecked(&self, cx: &mut JSContext, checked: bool) {
1104 self.update_checked_state(cx, checked, true);
1105 self.value_changed(cx);
1106 }
1107
1108 make_enumerated_getter!(
1110 ColorSpace,
1111 "colorspace",
1112 "limited-srgb" | "display-p3",
1113 missing => "limited-srgb",
1114 invalid => "limited-srgb"
1115 );
1116
1117 make_setter!(SetColorSpace, "colorspace");
1119
1120 make_bool_getter!(ReadOnly, "readonly");
1122
1123 make_bool_setter!(SetReadOnly, "readonly");
1125
1126 make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1128
1129 make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1131
1132 fn Type(&self) -> DOMString {
1134 DOMString::from(self.input_type().as_str())
1135 }
1136
1137 make_atomic_setter!(SetType, "type");
1139
1140 fn Value(&self) -> DOMString {
1142 match self.value_mode() {
1143 ValueMode::Value => self.textinput.borrow().get_content(),
1144 ValueMode::Default => self
1145 .upcast::<Element>()
1146 .get_attribute_string_value(&local_name!("value"))
1147 .map(|value| value.into())
1148 .unwrap_or_default(),
1149 ValueMode::DefaultOn => self
1150 .upcast::<Element>()
1151 .get_attribute_string_value(&local_name!("value"))
1152 .map(|value| value.into())
1153 .unwrap_or(DOMString::from("on")),
1154 ValueMode::Filename => {
1155 let mut path = DOMString::new();
1156 match self.input_type().as_specific().get_files() {
1157 Some(ref fl) => match fl.Item(0) {
1158 Some(ref f) => {
1159 path.push_str("C:\\fakepath\\");
1160 path.push_str(&f.name().str());
1161 path
1162 },
1163 None => path,
1164 },
1165 None => path,
1166 }
1167 },
1168 }
1169 }
1170
1171 fn SetValue(&self, cx: &mut JSContext, mut value: DOMString) -> ErrorResult {
1173 match self.value_mode() {
1174 ValueMode::Value => {
1175 {
1176 self.value_dirty.set(true);
1178
1179 self.sanitize_value(&mut value);
1182
1183 let mut textinput = self.textinput.borrow_mut();
1184
1185 if textinput.get_content() != value {
1190 textinput.set_content(value);
1192
1193 textinput.clear_selection_to_end();
1194 }
1195 }
1196
1197 self.update_placeholder_shown_state();
1201 self.maybe_update_shared_selection();
1202 },
1203 ValueMode::Default | ValueMode::DefaultOn => {
1204 self.upcast::<Element>()
1205 .set_string_attribute(cx, &local_name!("value"), value);
1206 },
1207 ValueMode::Filename => {
1208 if value.is_empty() {
1209 let window = self.owner_window();
1210 let fl = FileList::new(cx, &window, vec![]);
1211 self.input_type().as_specific().set_files(&fl)
1212 } else {
1213 return Err(Error::InvalidState(Some(
1214 "Non-empty value provided for filename".into(),
1215 )));
1216 }
1217 },
1218 }
1219
1220 self.value_changed(cx);
1221 self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
1222 Ok(())
1223 }
1224
1225 make_getter!(DefaultValue, "value");
1227
1228 make_setter!(SetDefaultValue, "value");
1230
1231 make_getter!(Min, "min");
1233
1234 make_setter!(SetMin, "min");
1236
1237 fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1239 self.suggestions_source_element()
1240 }
1241
1242 #[expect(unsafe_code)]
1244 fn GetValueAsDate(&self, cx: &mut JSContext, mut return_value: MutableHandleObject) {
1245 if let Some(date_time) = self
1246 .input_type()
1247 .as_specific()
1248 .convert_string_to_naive_datetime(self.Value())
1249 {
1250 let time = ClippedTime {
1251 t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1252 };
1253 return_value.set(unsafe { NewDateObject(cx, time) });
1254 }
1255 }
1256
1257 #[expect(unsafe_code)]
1259 fn SetValueAsDate(&self, cx: &mut JSContext, value: *mut JSObject) -> ErrorResult {
1260 rooted!(&in(cx) let value = value);
1261 if !self.does_value_as_date_apply() {
1262 return Err(Error::InvalidState(Some(
1263 "Input element cannot be treated as a date".into(),
1264 )));
1265 }
1266 if value.is_null() {
1267 return self.SetValue(cx, DOMString::new());
1268 }
1269 let mut msecs: f64 = 0.0;
1270 unsafe {
1274 let mut is_date = false;
1275 if !ObjectIsDate(cx, value.handle(), &mut is_date) {
1276 return Err(Error::JSFailed);
1277 }
1278 if !is_date {
1279 return Err(Error::Type(c"Value was not a date".to_owned()));
1280 }
1281 if !DateGetMsecSinceEpoch(cx, value.handle(), &mut msecs) {
1282 return Err(Error::JSFailed);
1283 }
1284 if !msecs.is_finite() {
1285 return self.SetValue(cx, DOMString::new());
1286 }
1287 }
1288
1289 let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1290 return self.SetValue(cx, DOMString::new());
1291 };
1292 self.SetValue(
1293 cx,
1294 self.input_type()
1295 .as_specific()
1296 .convert_datetime_to_dom_string(date_time),
1297 )
1298 }
1299
1300 fn ValueAsNumber(&self) -> f64 {
1302 self.convert_string_to_number(&self.Value().str())
1303 .unwrap_or(f64::NAN)
1304 }
1305
1306 fn SetValueAsNumber(&self, cx: &mut JSContext, value: f64) -> ErrorResult {
1308 if value.is_infinite() {
1309 Err(Error::Type(c"value is not finite".to_owned()))
1310 } else if !self.does_value_as_number_apply() {
1311 Err(Error::InvalidState(Some(
1312 "Input element value cannot be treated as a number".into(),
1313 )))
1314 } else if value.is_nan() {
1315 self.SetValue(cx, DOMString::new())
1316 } else if let Some(converted) = self.convert_number_to_string(value) {
1317 self.SetValue(cx, converted)
1318 } else {
1319 self.SetValue(cx, DOMString::new())
1324 }
1325 }
1326
1327 make_getter!(Name, "name");
1329
1330 make_atomic_setter!(SetName, "name");
1332
1333 make_getter!(Placeholder, "placeholder");
1335
1336 make_setter!(SetPlaceholder, "placeholder");
1338
1339 make_form_action_getter!(FormAction, "formaction");
1341
1342 make_setter!(SetFormAction, "formaction");
1344
1345 make_enumerated_getter!(
1347 FormEnctype,
1348 "formenctype",
1349 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1350 invalid => "application/x-www-form-urlencoded"
1351 );
1352
1353 make_setter!(SetFormEnctype, "formenctype");
1355
1356 make_enumerated_getter!(
1358 FormMethod,
1359 "formmethod",
1360 "get" | "post" | "dialog",
1361 invalid => "get"
1362 );
1363
1364 make_setter!(SetFormMethod, "formmethod");
1366
1367 make_getter!(FormTarget, "formtarget");
1369
1370 make_setter!(SetFormTarget, "formtarget");
1372
1373 make_bool_getter!(FormNoValidate, "formnovalidate");
1375
1376 make_bool_setter!(SetFormNoValidate, "formnovalidate");
1378
1379 make_getter!(Max, "max");
1381
1382 make_setter!(SetMax, "max");
1384
1385 make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1387
1388 make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1390
1391 make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1393
1394 make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1396
1397 make_bool_getter!(Multiple, "multiple");
1399
1400 make_bool_setter!(SetMultiple, "multiple");
1402
1403 make_getter!(Pattern, "pattern");
1405
1406 make_setter!(SetPattern, "pattern");
1408
1409 make_bool_getter!(Required, "required");
1411
1412 make_bool_setter!(SetRequired, "required");
1414
1415 make_url_getter!(Src, "src");
1417
1418 make_url_setter!(SetSrc, "src");
1420
1421 make_getter!(Step, "step");
1423
1424 make_setter!(SetStep, "step");
1426
1427 make_getter!(UseMap, "usemap");
1429
1430 make_setter!(SetUseMap, "usemap");
1432
1433 fn Indeterminate(&self) -> bool {
1435 self.upcast::<Element>()
1436 .state()
1437 .contains(ElementState::INDETERMINATE)
1438 }
1439
1440 fn SetIndeterminate(&self, _cx: &mut JSContext, val: bool) {
1442 self.upcast::<Element>()
1443 .set_state(ElementState::INDETERMINATE, val)
1444 }
1445
1446 fn GetLabels(&self, cx: &mut JSContext) -> Option<DomRoot<NodeList>> {
1450 if matches!(*self.input_type(), InputType::Hidden(_)) {
1451 None
1452 } else {
1453 Some(self.labels_node_list.or_init(|| {
1454 NodeList::new_labels_list(
1455 cx,
1456 self.upcast::<Node>().owner_doc().window(),
1457 self.upcast::<HTMLElement>(),
1458 )
1459 }))
1460 }
1461 }
1462
1463 fn Select(&self) {
1465 self.selection().dom_select();
1466 }
1467
1468 fn GetSelectionStart(&self) -> Option<u32> {
1470 self.selection().dom_start().map(|start| start.0 as u32)
1471 }
1472
1473 fn SetSelectionStart(&self, _cx: &mut JSContext, start: Option<u32>) -> ErrorResult {
1475 self.selection()
1476 .set_dom_start(start.map(Utf16CodeUnits::from))
1477 }
1478
1479 fn GetSelectionEnd(&self) -> Option<u32> {
1481 self.selection().dom_end().map(|end| end.0 as u32)
1482 }
1483
1484 fn SetSelectionEnd(&self, _cx: &mut JSContext, end: Option<u32>) -> ErrorResult {
1486 self.selection().set_dom_end(end.map(Utf16CodeUnits::from))
1487 }
1488
1489 fn GetSelectionDirection(&self) -> Option<DOMString> {
1491 self.selection().dom_direction()
1492 }
1493
1494 fn SetSelectionDirection(
1496 &self,
1497 _cx: &mut JSContext,
1498 direction: Option<DOMString>,
1499 ) -> ErrorResult {
1500 self.selection().set_dom_direction(direction)
1501 }
1502
1503 fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
1505 self.selection().set_dom_range(
1506 Utf16CodeUnits::from(start),
1507 Utf16CodeUnits::from(end),
1508 direction,
1509 )
1510 }
1511
1512 fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
1514 self.selection()
1515 .set_dom_range_text(replacement, None, None, Default::default())
1516 }
1517
1518 fn SetRangeText_(
1520 &self,
1521 replacement: DOMString,
1522 start: u32,
1523 end: u32,
1524 selection_mode: SelectionMode,
1525 ) -> ErrorResult {
1526 self.selection().set_dom_range_text(
1527 replacement,
1528 Some(Utf16CodeUnits::from(start)),
1529 Some(Utf16CodeUnits::from(end)),
1530 selection_mode,
1531 )
1532 }
1533
1534 fn SelectFiles(&self, paths: Vec<DOMString>) {
1537 self.input_type()
1538 .as_specific()
1539 .select_files(self, Some(paths));
1540 }
1541
1542 fn StepUp(&self, cx: &mut JSContext, n: i32) -> ErrorResult {
1544 self.step_up_or_down(cx, n, StepDirection::Up)
1545 }
1546
1547 fn StepDown(&self, cx: &mut JSContext, n: i32) -> ErrorResult {
1549 self.step_up_or_down(cx, n, StepDirection::Down)
1550 }
1551
1552 fn WillValidate(&self) -> bool {
1554 self.is_instance_validatable()
1555 }
1556
1557 fn Validity(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
1559 self.validity_state(cx)
1560 }
1561
1562 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
1564 self.check_validity(cx)
1565 }
1566
1567 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
1569 self.report_validity(cx)
1570 }
1571
1572 fn ValidationMessage(&self, cx: &mut JSContext) -> DOMString {
1574 self.validation_message(cx)
1575 }
1576
1577 fn SetCustomValidity(&self, cx: &mut JSContext, error: DOMString) {
1579 self.validity_state(cx).set_custom_error_message(cx, error);
1580 }
1581}
1582
1583impl HTMLInputElement {
1584 pub(crate) fn form_datums(
1587 &self,
1588 submitter: Option<FormSubmitterElement>,
1589 encoding: Option<&'static Encoding>,
1590 ) -> (Vec<FormDatum>, bool) {
1591 let ty = self.Type();
1592 let name = self.Name();
1593 let is_submitter = match submitter {
1594 Some(FormSubmitterElement::Input(s)) => self == s,
1595 _ => false,
1596 };
1597
1598 match *self.input_type() {
1600 InputType::Submit(_) | InputType::Button(_) | InputType::Reset(_) if !is_submitter => {
1602 return (vec![], true);
1603 },
1604
1605 InputType::Radio(_) | InputType::Checkbox(_) if !self.Checked() => {
1607 return (vec![], true);
1608 },
1609
1610 InputType::Image(_) => return (vec![], true), _ => {
1615 if name.is_empty() {
1616 return (vec![], true);
1617 }
1618 },
1619 }
1620
1621 let datums = match *self.input_type() {
1622 InputType::Checkbox(_) | InputType::Radio(_) => {
1624 let field_value = self.Value();
1626 let value = if field_value.is_empty() {
1627 DOMString::from_static("on")
1628 } else {
1629 field_value
1630 };
1631 vec![FormDatum {
1633 ty,
1634 name,
1635 value: FormDatumValue::String(value),
1636 }]
1637 },
1638
1639 InputType::File(_) => {
1641 let mut datums = vec![];
1642
1643 let name = self.Name();
1645
1646 match self.GetFiles() {
1647 None => {
1649 datums.push(FormDatum {
1650 ty,
1653 name,
1654 value: FormDatumValue::String(DOMString::from("")),
1655 })
1656 },
1657 Some(fl) => {
1659 for f in fl.iter_files() {
1660 datums.push(FormDatum {
1661 ty: ty.clone(),
1662 name: name.clone(),
1663 value: FormDatumValue::File(DomRoot::from_ref(f)),
1664 });
1665 }
1666 },
1667 }
1668
1669 datums
1670 },
1671
1672 InputType::Hidden(_) if name.eq_ignore_ascii_case("_charset_") => {
1674 let charset = match encoding {
1676 None => DOMString::from_static("UTF-8"),
1677 Some(enc) => DOMString::from(enc.name()),
1678 };
1679 vec![FormDatum {
1681 ty,
1682 name,
1683 value: FormDatumValue::String(charset),
1684 }]
1685 },
1686
1687 _ => vec![FormDatum {
1689 ty,
1690 name,
1691 value: FormDatumValue::String(self.Value()),
1692 }],
1693 };
1694 (datums, false)
1695 }
1696
1697 pub(crate) fn radio_group_name(&self) -> Option<Atom> {
1699 self.upcast::<Element>()
1700 .get_name()
1701 .filter(|name| !name.is_empty())
1702 }
1703
1704 fn update_checked_state(&self, cx: &mut JSContext, checked: bool, dirty: bool) {
1705 self.upcast::<Element>()
1706 .set_state(ElementState::CHECKED, checked);
1707
1708 if dirty {
1709 self.checked_changed.set(true);
1710 }
1711
1712 if matches!(*self.input_type(), InputType::Radio(_)) && checked {
1713 broadcast_radio_checked(cx, self, self.radio_group_name().as_ref());
1714 }
1715
1716 self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
1717 }
1718
1719 pub(crate) fn is_mutable(&self) -> bool {
1721 !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
1724 }
1725
1726 pub(crate) fn reset(&self, cx: &mut JSContext) {
1736 self.value_dirty.set(false);
1737
1738 let mut value = self.DefaultValue();
1740 self.sanitize_value(&mut value);
1741 self.textinput.borrow_mut().set_content(value);
1742
1743 let input_type = &*self.input_type();
1744 if matches!(input_type, InputType::Radio(_) | InputType::Checkbox(_)) {
1745 self.update_checked_state(cx, self.DefaultChecked(), false);
1746 self.checked_changed.set(false);
1747 }
1748
1749 if matches!(input_type, InputType::File(_)) {
1750 input_type
1751 .as_specific()
1752 .set_files(&FileList::new(cx, &self.owner_window(), vec![]));
1753 }
1754
1755 self.value_changed(cx);
1756 }
1757
1758 pub(crate) fn clear(&self, cx: &mut JSContext) {
1761 self.value_dirty.set(false);
1763 self.checked_changed.set(false);
1764 self.textinput.borrow_mut().set_content(DOMString::new());
1766 self.update_checked_state(cx, self.DefaultChecked(), false);
1768 if self.input_type().as_specific().get_files().is_some() {
1770 let window = self.owner_window();
1771 let filelist = FileList::new(cx, &window, vec![]);
1772 self.input_type().as_specific().set_files(&filelist);
1773 }
1774
1775 {
1778 let mut textinput = self.textinput.borrow_mut();
1779 let mut value = textinput.get_content();
1780 self.sanitize_value(&mut value);
1781 textinput.set_content(value);
1782 }
1783
1784 self.value_changed(cx);
1785 }
1786
1787 fn update_placeholder_shown_state(&self) {
1788 if !self.input_type().is_textual_or_password() {
1789 self.upcast::<Element>().set_placeholder_shown_state(false);
1790 } else {
1791 let has_placeholder = !self.placeholder.borrow().is_empty();
1792 let has_value = !self.textinput.borrow().is_empty();
1793 self.upcast::<Element>()
1794 .set_placeholder_shown_state(has_placeholder && !has_value);
1795 }
1796 }
1797
1798 pub(crate) fn select_files_for_webdriver(
1799 &self,
1800 test_paths: Vec<DOMString>,
1801 response_sender: GenericSender<Result<bool, ErrorStatus>>,
1802 ) {
1803 let mut stored_sender = self.pending_webdriver_response.borrow_mut();
1804 assert!(stored_sender.is_none());
1805
1806 *stored_sender = Some(PendingWebDriverResponse {
1807 response_sender,
1808 expected_file_count: test_paths.len(),
1809 });
1810
1811 self.input_type()
1812 .as_specific()
1813 .select_files(self, Some(test_paths));
1814 }
1815
1816 pub(crate) fn take_pending_webdriver_response(&self) -> Option<PendingWebDriverResponse> {
1817 self.pending_webdriver_response.borrow_mut().take()
1818 }
1819
1820 fn sanitize_value(&self, value: &mut DOMString) {
1822 self.input_type().as_specific().sanitize_value(self, value);
1823 }
1824
1825 fn selection(&self) -> TextControlSelection<'_, Self> {
1826 TextControlSelection::new(self, &self.textinput)
1827 }
1828
1829 fn implicit_submission(&self, cx: &mut JSContext) {
1831 let doc = self.owner_document();
1832 let node = doc.upcast::<Node>();
1833 let owner = self.form_owner();
1834 let form = match owner {
1835 None => return,
1836 Some(ref f) => f,
1837 };
1838
1839 if self.upcast::<Element>().click_in_progress() {
1840 return;
1841 }
1842 let submit_button = node
1843 .traverse_preorder(ShadowIncluding::No)
1844 .filter_map(DomRoot::downcast::<HTMLInputElement>)
1845 .filter(|input| matches!(*input.input_type(), InputType::Submit(_)))
1846 .find(|r| r.form_owner() == owner);
1847 match submit_button {
1848 Some(ref button) => {
1849 if button.is_instance_activatable() {
1850 button
1853 .upcast::<Node>()
1854 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
1855 }
1856 },
1857 None => {
1858 let mut inputs = node
1859 .traverse_preorder(ShadowIncluding::No)
1860 .filter_map(DomRoot::downcast::<HTMLInputElement>)
1861 .filter(|input| {
1862 input.form_owner() == owner &&
1863 matches!(
1864 *input.input_type(),
1865 InputType::Text(_) |
1866 InputType::Search(_) |
1867 InputType::Url(_) |
1868 InputType::Tel(_) |
1869 InputType::Email(_) |
1870 InputType::Password(_) |
1871 InputType::Date(_) |
1872 InputType::Month(_) |
1873 InputType::Week(_) |
1874 InputType::Time(_) |
1875 InputType::DatetimeLocal(_) |
1876 InputType::Number(_)
1877 )
1878 });
1879
1880 if inputs.nth(1).is_some() {
1881 return;
1883 }
1884 form.submit(
1885 cx,
1886 SubmittedFrom::NotFromForm,
1887 FormSubmitterElement::Form(form),
1888 );
1889 },
1890 }
1891 }
1892
1893 pub(crate) fn convert_string_to_number(&self, value: &str) -> Option<f64> {
1895 self.input_type()
1896 .as_specific()
1897 .convert_string_to_number(value)
1898 }
1899
1900 fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
1902 self.input_type()
1903 .as_specific()
1904 .convert_number_to_string(value)
1905 }
1906
1907 fn update_related_validity_states(&self, cx: &mut JSContext) {
1908 match *self.input_type() {
1909 InputType::Radio(_) => {
1910 perform_radio_group_validation(cx, self, self.radio_group_name().as_ref())
1911 },
1912 _ => {
1913 self.validity_state(cx)
1914 .perform_validation_and_update(cx, ValidationFlags::all());
1915 },
1916 }
1917 }
1918
1919 fn value_changed(&self, cx: &mut JSContext) {
1920 self.maybe_update_shared_selection();
1921 self.update_related_validity_states(cx);
1922 self.input_type().as_specific().update_shadow_tree(cx, self);
1923 }
1924
1925 pub(crate) fn show_the_picker_if_applicable(&self) {
1927 if !self.is_mutable() {
1931 return;
1932 }
1933
1934 self.input_type()
1937 .as_specific()
1938 .show_the_picker_if_applicable(self);
1939 }
1940
1941 pub(crate) fn handle_color_picker_response(
1942 &self,
1943 cx: &mut JSContext,
1944 response: Option<RgbColor>,
1945 ) {
1946 if let InputType::Color(ref color_input_type) = *self.input_type() {
1947 color_input_type.handle_color_picker_response(cx, self, response)
1948 }
1949 }
1950
1951 pub(crate) fn handle_file_picker_response(
1952 &self,
1953 cx: &mut JSContext,
1954 response: Option<Vec<SelectedFile>>,
1955 ) {
1956 if let InputType::File(ref file_input_type) = *self.input_type() {
1957 file_input_type.handle_file_picker_response(cx, self, response)
1958 }
1959 }
1960
1961 fn handle_focus_event(&self, event: &FocusEvent) {
1962 let event_type = event.upcast::<Event>().type_();
1963 if *event_type == *"blur" {
1964 self.owner_document()
1965 .embedder_controls()
1966 .hide_embedder_control(self.upcast());
1967 } else if *event_type == *"focus" {
1968 let input_type = &*self.input_type();
1969 let Ok(input_method_type) = input_type.try_into() else {
1970 return;
1971 };
1972
1973 self.owner_document()
1974 .embedder_controls()
1975 .show_embedder_control(
1976 ControlElement::Ime(Dom::from_ref(self.upcast())),
1977 EmbedderControlRequest::InputMethod(InputMethodRequest {
1978 input_method_type,
1979 text: String::from(self.Value()),
1980 insertion_point: self.GetSelectionEnd(),
1981 multiline: false,
1982 allow_virtual_keyboard: self.owner_window().has_sticky_activation(),
1984 }),
1985 None,
1986 );
1987 }
1988 }
1989}
1990
1991impl VirtualMethods for HTMLInputElement {
1992 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1993 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1994 }
1995
1996 fn attribute_mutated(
1997 &self,
1998 cx: &mut JSContext,
1999 attr: AttrRef<'_>,
2000 mutation: AttributeMutation,
2001 ) {
2002 let could_have_had_embedder_control = self.may_have_embedder_control();
2003
2004 self.super_type()
2005 .unwrap()
2006 .attribute_mutated(cx, attr, mutation);
2007
2008 match *attr.local_name() {
2009 local_name!("disabled") => {
2010 let disabled_state = match mutation {
2011 AttributeMutation::Set(None, _) => true,
2012 AttributeMutation::Set(Some(_), _) => {
2013 return;
2015 },
2016 AttributeMutation::Removed => false,
2017 };
2018 let el = self.upcast::<Element>();
2019 el.set_disabled_state(disabled_state);
2020 el.set_enabled_state(!disabled_state);
2021 el.check_ancestors_disabled_state_for_form_control();
2022
2023 if self.input_type().is_textual() {
2024 let read_write = !(self.ReadOnly() || el.disabled_state());
2025 el.set_read_write_state(read_write);
2026 }
2027 },
2028 local_name!("checked") if !self.checked_changed.get() => {
2029 let checked_state = match mutation {
2030 AttributeMutation::Set(None, _) => true,
2031 AttributeMutation::Set(Some(_), _) => {
2032 return;
2034 },
2035 AttributeMutation::Removed => false,
2036 };
2037 self.update_checked_state(cx, checked_state, false);
2038 },
2039 local_name!("size") => {
2040 let size = mutation.new_value(attr).map(|value| value.as_uint());
2041 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
2042 },
2043 local_name!("type") => {
2044 match mutation {
2045 AttributeMutation::Set(previous_value, _) => {
2046 if previous_value
2050 .is_some_and(|previous_value| **previous_value == **attr.value())
2051 {
2052 return;
2053 }
2054
2055 let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
2056 let previously_selectable = self.selection_api_applies();
2057
2058 *self.input_type.borrow_mut() =
2059 InputType::new_from_atom(attr.value().as_atom());
2060 self.is_textual_or_password
2061 .set(self.input_type().is_textual_or_password());
2062
2063 let element = self.upcast::<Element>();
2064 if self.input_type().is_textual() {
2065 let read_write = !(self.ReadOnly() || element.disabled_state());
2066 element.set_read_write_state(read_write);
2067 } else {
2068 element.set_read_write_state(false);
2069 }
2070
2071 let new_value_mode = self.value_mode();
2072 match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
2073 (&ValueMode::Value, false, ValueMode::Default) |
2075 (&ValueMode::Value, false, ValueMode::DefaultOn) => {
2076 self.SetValue(cx, old_idl_value)
2077 .expect("Failed to set input value on type change to a default ValueMode.");
2078 },
2079
2080 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
2082 self.SetValue(
2083 cx,
2084 self.upcast::<Element>()
2085 .get_attribute_string_value(&local_name!("value"))
2086 .unwrap_or_default()
2087 .into(),
2088 )
2089 .expect(
2090 "Failed to set input value on type change to ValueMode::Value.",
2091 );
2092 self.value_dirty.set(false);
2093 },
2094
2095 (_, _, ValueMode::Filename)
2097 if old_value_mode != ValueMode::Filename =>
2098 {
2099 self.SetValue(cx, DOMString::new())
2100 .expect("Failed to set input value on type change to ValueMode::Filename.");
2101 },
2102 _ => {},
2103 }
2104
2105 self.input_type().as_specific().signal_type_change(cx, self);
2107
2108 let mut textinput = self.textinput.borrow_mut();
2110 let mut value = textinput.get_content();
2111 self.sanitize_value(&mut value);
2112 textinput.set_content(value);
2113 self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
2114
2115 if self.does_minmaxlength_apply() {
2117 textinput
2118 .set_min_length(self.MinLength().to_usize().map(Utf16CodeUnits));
2119 textinput
2120 .set_max_length(self.MaxLength().to_usize().map(Utf16CodeUnits));
2121 } else {
2122 textinput.set_min_length(None);
2123 textinput.set_max_length(None);
2124 }
2125
2126 if !previously_selectable && self.selection_api_applies() {
2128 textinput.clear_selection_to_start();
2129 }
2130 },
2131 AttributeMutation::Removed => {
2132 self.input_type().as_specific().signal_type_change(cx, self);
2133 *self.input_type.borrow_mut() = InputType::new_text();
2134 self.is_textual_or_password
2135 .set(self.input_type().is_textual_or_password());
2136
2137 let element = self.upcast::<Element>();
2138 let read_write = !(self.ReadOnly() || element.disabled_state());
2139 element.set_read_write_state(read_write);
2140 },
2141 }
2142
2143 self.update_placeholder_shown_state();
2144 self.input_type()
2145 .as_specific()
2146 .update_placeholder_contents(cx, self);
2147 },
2148 local_name!("value") if !self.value_dirty.get() => {
2149 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
2153 let mut value = value.map_or(DOMString::new(), DOMString::from);
2154
2155 self.sanitize_value(&mut value);
2156 self.textinput.borrow_mut().set_content(value);
2157 self.update_placeholder_shown_state();
2158 },
2159 local_name!("maxlength") if self.does_minmaxlength_apply() => match *attr.value() {
2160 AttrValue::Int(_, value) => {
2161 let mut textinput = self.textinput.borrow_mut();
2162
2163 if value < 0 {
2164 textinput.set_max_length(None);
2165 } else {
2166 textinput.set_max_length(Some(Utf16CodeUnits(value as usize)))
2167 }
2168 },
2169 _ => panic!("Expected an AttrValue::Int"),
2170 },
2171 local_name!("minlength") if self.does_minmaxlength_apply() => match *attr.value() {
2172 AttrValue::Int(_, value) => {
2173 let mut textinput = self.textinput.borrow_mut();
2174
2175 if value < 0 {
2176 textinput.set_min_length(None);
2177 } else {
2178 textinput.set_min_length(Some(Utf16CodeUnits(value as usize)))
2179 }
2180 },
2181 _ => panic!("Expected an AttrValue::Int"),
2182 },
2183 local_name!("placeholder") => {
2184 {
2185 let mut placeholder = self.placeholder.borrow_mut();
2186 placeholder.clear();
2187 if let AttributeMutation::Set(..) = mutation {
2188 placeholder
2189 .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
2190 }
2191 }
2192 self.update_placeholder_shown_state();
2193 self.input_type()
2194 .as_specific()
2195 .update_placeholder_contents(cx, self);
2196 },
2197 local_name!("readonly") => {
2198 if self.input_type().is_textual() {
2199 let el = self.upcast::<Element>();
2200 match mutation {
2201 AttributeMutation::Set(..) => {
2202 el.set_read_write_state(false);
2203 },
2204 AttributeMutation::Removed => {
2205 el.set_read_write_state(!el.disabled_state());
2206 },
2207 }
2208 }
2209 },
2210 local_name!("form") => {
2211 self.form_attribute_mutated(cx, mutation);
2212 },
2213 _ => {
2214 self.input_type()
2215 .as_specific()
2216 .attribute_mutated(cx, self, attr, mutation);
2217 },
2218 }
2219
2220 self.value_changed(cx);
2221
2222 if could_have_had_embedder_control && !self.may_have_embedder_control() {
2223 self.owner_document()
2224 .embedder_controls()
2225 .hide_embedder_control(self.upcast());
2226 }
2227 }
2228
2229 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
2230 match *name {
2231 local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
2232 local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
2233 local_name!("type") => AttrValue::from_atomic(value.into()),
2234 local_name!("maxlength") => {
2235 AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
2236 },
2237 local_name!("minlength") => {
2238 AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
2239 },
2240 _ => self
2241 .super_type()
2242 .unwrap()
2243 .parse_plain_attribute(name, value),
2244 }
2245 }
2246
2247 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
2248 if let Some(s) = self.super_type() {
2249 s.bind_to_tree(cx, context);
2250 }
2251 self.upcast::<Element>()
2252 .check_ancestors_disabled_state_for_form_control();
2253
2254 self.input_type()
2255 .as_specific()
2256 .bind_to_tree(cx, self, context);
2257
2258 self.value_changed(cx);
2259 }
2260
2261 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
2262 self.owner_document()
2264 .embedder_controls()
2265 .hide_embedder_control(self.upcast());
2266
2267 let form_owner = self.form_owner();
2268 self.super_type().unwrap().unbind_from_tree(cx, context);
2269
2270 let node = self.upcast::<Node>();
2271 let el = self.upcast::<Element>();
2272 if node
2273 .ancestors()
2274 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
2275 {
2276 el.check_ancestors_disabled_state_for_form_control();
2277 } else {
2278 el.check_disabled_attribute();
2279 }
2280
2281 self.input_type()
2282 .as_specific()
2283 .unbind_from_tree(cx, self, form_owner, context);
2284
2285 self.validity_state(cx)
2286 .perform_validation_and_update(cx, ValidationFlags::all());
2287 }
2288
2289 fn handle_event(&self, cx: &mut JSContext, event: &Event) {
2295 if event.type_() == atom!("keydown") &&
2296 !event.DefaultPrevented() &&
2297 self.input_type().is_textual_or_password()
2298 {
2299 if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
2300 let action = self.textinput.borrow_mut().handle_keydown(keyevent);
2303 self.handle_key_reaction(cx, action, event);
2304 }
2305 } else if (event.type_() == atom!("compositionstart") ||
2306 event.type_() == atom!("compositionupdate") ||
2307 event.type_() == atom!("compositionend")) &&
2308 self.input_type().is_textual_or_password()
2309 {
2310 if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
2311 if event.type_() == atom!("compositionend") {
2312 let action = self
2313 .textinput
2314 .borrow_mut()
2315 .handle_compositionend(compositionevent);
2316 self.handle_key_reaction(cx, action, event);
2317 self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
2318 self.update_placeholder_shown_state();
2319 } else if event.type_() == atom!("compositionupdate") {
2320 let action = self
2321 .textinput
2322 .borrow_mut()
2323 .handle_compositionupdate(compositionevent);
2324 self.handle_key_reaction(cx, action, event);
2325 self.upcast::<Node>().dirty(cx.no_gc(), NodeDamage::Other);
2326 self.update_placeholder_shown_state();
2327 } else if event.type_() == atom!("compositionstart") {
2328 self.update_placeholder_shown_state();
2330 }
2331 event.mark_as_handled();
2332 }
2333 } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
2334 let reaction = self
2335 .textinput
2336 .borrow_mut()
2337 .handle_clipboard_event(clipboard_event);
2338 let flags = reaction.flags;
2339 if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
2340 self.owner_document().event_handler().fire_clipboard_event(
2341 cx,
2342 None,
2343 ClipboardEventType::Change,
2344 );
2345 }
2346 if flags.contains(ClipboardEventFlags::QueueInputEvent) {
2347 self.textinput.borrow().queue_input_event(
2348 self.upcast(),
2349 reaction.text,
2350 IsComposing::NotComposing,
2351 reaction.input_type,
2352 );
2353 }
2354 if !flags.is_empty() {
2355 event.mark_as_handled();
2356 self.upcast::<Node>()
2357 .dirty(cx.no_gc(), NodeDamage::ContentOrHeritage);
2358 }
2359 } else if let Some(event) = event.downcast::<FocusEvent>() {
2360 self.handle_focus_event(event)
2361 }
2362
2363 self.value_changed(cx);
2364
2365 if let Some(super_type) = self.super_type() {
2366 super_type.handle_event(cx, event);
2367 }
2368 }
2369
2370 fn handle_mousedown_event(
2371 &self,
2372 cx: &mut JSContext,
2373 mouse_event: &MouseEvent,
2374 hit_test_result: &HitTestResult,
2375 ) {
2376 if !self.input_type().is_textual_or_password() || self.textinput.borrow().is_empty() {
2379 if let Some(super_type) = self.super_type() {
2380 super_type.handle_mousedown_event(cx, mouse_event, hit_test_result);
2381 }
2382 return;
2383 }
2384
2385 if self.textinput.borrow_mut().handle_mousedown_event(
2386 self.upcast(),
2387 mouse_event,
2388 hit_test_result,
2389 ) {
2390 self.maybe_update_shared_selection();
2391 mouse_event.upcast::<Event>().mark_as_handled();
2392 }
2393 }
2394
2395 fn cloning_steps(
2397 &self,
2398 cx: &mut JSContext,
2399 copy: &Node,
2400 maybe_doc: Option<&Document>,
2401 clone_children: CloneChildrenFlag,
2402 ) {
2403 if let Some(s) = self.super_type() {
2404 s.cloning_steps(cx, copy, maybe_doc, clone_children);
2405 }
2406 let elem = copy.downcast::<HTMLInputElement>().unwrap();
2407 elem.value_dirty.set(self.value_dirty.get());
2408 elem.checked_changed.set(self.checked_changed.get());
2409 elem.upcast::<Element>()
2410 .set_state(ElementState::CHECKED, self.Checked());
2411 elem.upcast::<Element>()
2414 .set_state(ElementState::INDETERMINATE, self.Indeterminate());
2415 elem.textinput
2416 .borrow_mut()
2417 .set_content(self.textinput.borrow().get_content());
2418 self.value_changed(cx);
2419 }
2420}
2421
2422impl FormControl for HTMLInputElement {
2423 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
2424 self.form_owner.get()
2425 }
2426
2427 fn set_form_owner(&self, _cx: &mut JSContext, form: Option<&HTMLFormElement>) {
2428 self.form_owner.set(form);
2429 }
2430
2431 fn to_html_element(&self) -> &HTMLElement {
2432 self.upcast::<HTMLElement>()
2433 }
2434}
2435
2436impl Validatable for HTMLInputElement {
2437 fn as_element(&self) -> &Element {
2438 self.upcast()
2439 }
2440
2441 fn validity_state(&self, cx: &mut JSContext) -> DomRoot<ValidityState> {
2442 self.validity_state
2443 .or_init(|| ValidityState::new(cx, &self.owner_window(), self.upcast()))
2444 }
2445
2446 fn is_instance_validatable(&self) -> bool {
2447 match *self.input_type() {
2454 InputType::Hidden(_) | InputType::Button(_) | InputType::Reset(_) => false,
2455 _ => {
2456 !(self.upcast::<Element>().disabled_state() ||
2457 self.ReadOnly() ||
2458 is_barred_by_datalist_ancestor(self.upcast()))
2459 },
2460 }
2461 }
2462
2463 fn perform_validation(
2464 &self,
2465 cx: &mut JSContext,
2466 validate_flags: ValidationFlags,
2467 ) -> ValidationFlags {
2468 let mut failed_flags = ValidationFlags::empty();
2469 let value = self.Value();
2470
2471 if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
2472 self.suffers_from_being_missing(&value)
2473 {
2474 failed_flags.insert(ValidationFlags::VALUE_MISSING);
2475 }
2476
2477 if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
2478 self.suffers_from_type_mismatch(&value)
2479 {
2480 failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
2481 }
2482
2483 if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
2484 self.suffers_from_pattern_mismatch(cx, &value)
2485 {
2486 failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
2487 }
2488
2489 if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
2490 self.suffers_from_bad_input(&value)
2491 {
2492 failed_flags.insert(ValidationFlags::BAD_INPUT);
2493 }
2494
2495 if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
2496 failed_flags |= self.suffers_from_length_issues(&value);
2497 }
2498
2499 if validate_flags.intersects(
2500 ValidationFlags::RANGE_UNDERFLOW |
2501 ValidationFlags::RANGE_OVERFLOW |
2502 ValidationFlags::STEP_MISMATCH,
2503 ) {
2504 failed_flags |= self.suffers_from_range_issues(&value);
2505 }
2506
2507 failed_flags & validate_flags
2508 }
2509}
2510
2511impl Activatable for HTMLInputElement {
2512 fn as_element(&self) -> &Element {
2513 self.upcast()
2514 }
2515
2516 fn is_instance_activatable(&self) -> bool {
2517 match *self.input_type() {
2518 InputType::Submit(_) |
2525 InputType::Reset(_) |
2526 InputType::File(_) |
2527 InputType::Image(_) |
2528 InputType::Button(_) => self.is_mutable(),
2529 InputType::Checkbox(_) | InputType::Radio(_) | InputType::Color(_) => true,
2533 _ => false,
2534 }
2535 }
2536
2537 fn legacy_pre_activation_behavior(&self, cx: &mut JSContext) -> Option<InputActivationState> {
2539 let activation_state = self
2540 .input_type()
2541 .as_specific()
2542 .legacy_pre_activation_behavior(cx, self);
2543
2544 if activation_state.is_some() {
2545 self.value_changed(cx);
2546 }
2547
2548 activation_state
2549 }
2550
2551 fn legacy_canceled_activation_behavior(
2553 &self,
2554 cx: &mut JSContext,
2555 cache: Option<InputActivationState>,
2556 ) {
2557 let ty = self.input_type();
2559 let cache = match cache {
2560 Some(cache) => {
2561 if (cache.was_radio && !matches!(*ty, InputType::Radio(_))) ||
2562 (cache.was_checkbox && !matches!(*ty, InputType::Checkbox(_)))
2563 {
2564 return;
2567 }
2568 cache
2569 },
2570 None => {
2571 return;
2572 },
2573 };
2574
2575 ty.as_specific()
2577 .legacy_canceled_activation_behavior(cx, self, cache);
2578
2579 self.value_changed(cx);
2580 }
2581
2582 fn activation_behavior(&self, cx: &mut JSContext, event: &Event, target: &EventTarget) {
2584 let input_activation_type = {
2585 let input_type = self.input_type();
2586 InputActivationType::new_from_input_type(&input_type)
2587 };
2588
2589 if let Some(input_activation_type) = input_activation_type {
2590 input_activation_type
2591 .as_specific()
2592 .activation_behavior(cx, self, event, target);
2593 }
2594 }
2595}
2596
2597fn compile_pattern(cx: &mut JSContext, pattern_str: &str, out_regex: MutableHandleObject) -> bool {
2601 if check_js_regex_syntax(cx, pattern_str) {
2603 let pattern_str = format!("^(?:{})$", pattern_str);
2605 let flags = RegExpFlags {
2606 flags_: RegExpFlag_UnicodeSets,
2607 };
2608 new_js_regex(cx, &pattern_str, flags, out_regex)
2609 } else {
2610 false
2611 }
2612}
2613
2614#[expect(unsafe_code)]
2615fn check_js_regex_syntax(cx: &mut JSContext, pattern: &str) -> bool {
2618 let pattern: Vec<u16> = pattern.encode_utf16().collect();
2619 rooted!(&in(cx) let mut exception = UndefinedValue());
2620
2621 let valid = unsafe {
2622 CheckRegExpSyntax(
2623 cx,
2624 pattern.as_ptr(),
2625 pattern.len(),
2626 RegExpFlags {
2627 flags_: RegExpFlag_UnicodeSets,
2628 },
2629 exception.handle_mut(),
2630 )
2631 };
2632
2633 if !valid {
2634 unsafe { JS_ClearPendingException(cx) };
2635 return false;
2636 }
2637
2638 exception.is_undefined()
2641}
2642
2643#[expect(unsafe_code)]
2644fn new_js_regex(
2645 cx: &mut JSContext,
2646 pattern: &str,
2647 flags: RegExpFlags,
2648 mut out_regex: MutableHandleObject,
2649) -> bool {
2650 let pattern: Vec<u16> = pattern.encode_utf16().collect();
2651 out_regex.set(unsafe { NewUCRegExpObject(cx, pattern.as_ptr(), pattern.len(), flags) });
2652
2653 if out_regex.is_null() {
2654 unsafe { JS_ClearPendingException(cx) };
2655 return false;
2656 }
2657 true
2658}
2659
2660#[expect(unsafe_code)]
2661fn matches_js_regex(cx: &mut JSContext, regex_obj: HandleObject, value: &str) -> Result<bool, ()> {
2662 let mut value: Vec<u16> = value.encode_utf16().collect();
2663
2664 let mut is_regex = false;
2665 assert!(unsafe { ObjectIsRegExp(cx, regex_obj, &mut is_regex) });
2666 assert!(is_regex);
2667
2668 rooted!(&in(cx) let mut rval = UndefinedValue());
2669 let mut index = 0;
2670
2671 let ok = unsafe {
2672 ExecuteRegExpNoStatics(
2673 cx,
2674 regex_obj,
2675 value.as_mut_ptr(),
2676 value.len(),
2677 &mut index,
2678 true,
2679 rval.handle_mut(),
2680 )
2681 };
2682
2683 if ok {
2684 Ok(!rval.is_null())
2685 } else {
2686 unsafe { JS_ClearPendingException(cx) };
2687 Err(())
2688 }
2689}
2690
2691#[derive(MallocSizeOf)]
2695pub(crate) struct PendingWebDriverResponse {
2696 response_sender: GenericSender<Result<bool, ErrorStatus>>,
2698 expected_file_count: usize,
2700}
2701
2702impl PendingWebDriverResponse {
2703 pub(crate) fn finish(self, number_files_selected: usize) {
2704 if number_files_selected == self.expected_file_count {
2705 let _ = self.response_sender.send(Ok(false));
2706 } else {
2707 let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
2710 }
2711 }
2712}