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