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