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::codegen::GenericBindings::AttrBinding::AttrMethods;
26use script_bindings::domstring::parse_floating_point_number;
27use servo_base::generic_channel::GenericSender;
28use servo_base::text::Utf16CodeUnitLength;
29use style::attr::AttrValue;
30use style::str::split_commas;
31use stylo_atoms::Atom;
32use stylo_dom::ElementState;
33use time::OffsetDateTime;
34use unicode_bidi::{BidiClass, bidi_class};
35use webdriver::error::ErrorStatus;
36
37use crate::clipboard_provider::EmbedderClipboardProvider;
38use crate::dom::activation::Activatable;
39use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
40use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
41use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
42use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode;
43use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
44use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
45use crate::dom::bindings::error::{Error, ErrorResult};
46use crate::dom::bindings::inheritance::Castable;
47use crate::dom::bindings::refcounted::Trusted;
48use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
49use crate::dom::bindings::str::{DOMString, USVString};
50use crate::dom::clipboardevent::{ClipboardEvent, ClipboardEventType};
51use crate::dom::compositionevent::CompositionEvent;
52use crate::dom::document::Document;
53use crate::dom::document_embedder_controls::ControlElement;
54use crate::dom::element::attributes::storage::AttrRef;
55use crate::dom::element::{AttributeMutation, Element};
56use crate::dom::event::Event;
57use crate::dom::event::event::{EventBubbles, EventCancelable, EventComposed};
58use crate::dom::eventtarget::EventTarget;
59use crate::dom::filelist::FileList;
60use crate::dom::globalscope::GlobalScope;
61use crate::dom::html::htmldatalistelement::HTMLDataListElement;
62use crate::dom::html::htmlelement::HTMLElement;
63use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
64use crate::dom::html::htmlformelement::{
65 FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, SubmittedFrom,
66};
67use crate::dom::htmlinputelement::radio_input_type::{
68 broadcast_radio_checked, perform_radio_group_validation,
69};
70use crate::dom::input_element::input_type::InputType;
71use crate::dom::keyboardevent::KeyboardEvent;
72use crate::dom::node::{
73 BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding, 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 = self.Value().to_string();
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(attribute) = self.upcast::<Element>().get_attribute(&local_name!("step")) else {
392 return Some(default_step * self.step_scale_factor());
393 };
394
395 if attribute.value().eq_ignore_ascii_case("any") {
398 return None;
399 }
400
401 let Some(parsed_value) =
405 parse_floating_point_number(&attribute.value()).filter(|value| *value > 0.0)
406 else {
407 return Some(default_step * self.step_scale_factor());
408 };
409
410 Some(parsed_value * self.step_scale_factor())
414 }
415
416 fn minimum(&self) -> Option<f64> {
418 self.upcast::<Element>()
419 .get_attribute(&local_name!("min"))
420 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
421 .or_else(|| self.default_minimum())
422 }
423
424 fn maximum(&self) -> Option<f64> {
426 self.upcast::<Element>()
427 .get_attribute(&local_name!("max"))
428 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
429 .or_else(|| self.default_maximum())
430 }
431
432 fn stepped_minimum(&self) -> Option<f64> {
435 match (self.minimum(), self.allowed_value_step()) {
436 (Some(min), Some(allowed_step)) => {
437 let step_base = self.step_base();
438 let nsteps = (min - step_base) / allowed_step;
440 Some(step_base + (allowed_step * nsteps.ceil()))
442 },
443 (_, _) => None,
444 }
445 }
446
447 fn stepped_maximum(&self) -> Option<f64> {
450 match (self.maximum(), self.allowed_value_step()) {
451 (Some(max), Some(allowed_step)) => {
452 let step_base = self.step_base();
453 let nsteps = (max - step_base) / allowed_step;
455 Some(step_base + (allowed_step * nsteps.floor()))
457 },
458 (_, _) => None,
459 }
460 }
461
462 fn default_minimum(&self) -> Option<f64> {
464 match *self.input_type() {
465 InputType::Range(_) => Some(0.0),
466 _ => None,
467 }
468 }
469
470 fn default_maximum(&self) -> Option<f64> {
472 match *self.input_type() {
473 InputType::Range(_) => Some(100.0),
474 _ => None,
475 }
476 }
477
478 fn default_range_value(&self) -> f64 {
480 let min = self.minimum().unwrap_or(0.0);
481 let max = self.maximum().unwrap_or(100.0);
482 if max < min {
483 min
484 } else {
485 min + (max - min) * 0.5
486 }
487 }
488
489 fn default_step(&self) -> Option<f64> {
491 match *self.input_type() {
492 InputType::Date(_) => Some(1.0),
493 InputType::Month(_) => Some(1.0),
494 InputType::Week(_) => Some(1.0),
495 InputType::Time(_) => Some(60.0),
496 InputType::DatetimeLocal(_) => Some(60.0),
497 InputType::Number(_) => Some(1.0),
498 InputType::Range(_) => Some(1.0),
499 _ => None,
500 }
501 }
502
503 fn step_scale_factor(&self) -> f64 {
505 match *self.input_type() {
506 InputType::Date(_) => 86400000.0,
507 InputType::Month(_) => 1.0,
508 InputType::Week(_) => 604800000.0,
509 InputType::Time(_) => 1000.0,
510 InputType::DatetimeLocal(_) => 1000.0,
511 InputType::Number(_) => 1.0,
512 InputType::Range(_) => 1.0,
513 _ => unreachable!(),
514 }
515 }
516
517 fn step_base(&self) -> f64 {
519 if let Some(minimum) = self
523 .upcast::<Element>()
524 .get_attribute(&local_name!("min"))
525 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
526 {
527 return minimum;
528 }
529
530 if let Some(value) = self
534 .upcast::<Element>()
535 .get_attribute(&local_name!("value"))
536 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
537 {
538 return value;
539 }
540
541 if let Some(default_step_base) = self.default_step_base() {
543 return default_step_base;
544 }
545
546 0.0
548 }
549
550 fn default_step_base(&self) -> Option<f64> {
552 match *self.input_type() {
553 InputType::Week(_) => Some(-259200000.0),
554 _ => None,
555 }
556 }
557
558 fn step_up_or_down(&self, cx: &mut JSContext, n: i32, dir: StepDirection) -> ErrorResult {
562 if !self.does_value_as_number_apply() {
565 return Err(Error::InvalidState(None));
566 }
567 let step_base = self.step_base();
568
569 let Some(allowed_value_step) = self.allowed_value_step() else {
571 return Err(Error::InvalidState(None));
572 };
573
574 let minimum = self.minimum();
577 let maximum = self.maximum();
578 if let (Some(min), Some(max)) = (minimum, maximum) {
579 if min > max {
580 return Ok(());
581 }
582
583 if let Some(stepped_minimum) = self.stepped_minimum() &&
587 stepped_minimum > max
588 {
589 return Ok(());
590 }
591 }
592
593 let mut value: f64 = self
597 .convert_string_to_number(&self.Value().str())
598 .unwrap_or(0.0);
599
600 let valueBeforeStepping = value;
602
603 if (value - step_base) % allowed_value_step != 0.0 {
608 value = match dir {
609 StepDirection::Down =>
610 {
612 let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
613 intervals_from_base * allowed_value_step + step_base
614 },
615 StepDirection::Up =>
616 {
618 let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
619 intervals_from_base * allowed_value_step + step_base
620 },
621 };
622 }
623 else {
625 value += match dir {
630 StepDirection::Down => -f64::from(n) * allowed_value_step,
631 StepDirection::Up => f64::from(n) * allowed_value_step,
632 };
633 }
634
635 if let Some(min) = minimum &&
639 value < min
640 {
641 value = self.stepped_minimum().unwrap_or(value);
642 }
643
644 if let Some(max) = maximum &&
648 value > max
649 {
650 value = self.stepped_maximum().unwrap_or(value);
651 }
652
653 match dir {
657 StepDirection::Down => {
658 if value > valueBeforeStepping {
659 return Ok(());
660 }
661 },
662 StepDirection::Up => {
663 if value < valueBeforeStepping {
664 return Ok(());
665 }
666 },
667 }
668
669 self.SetValueAsNumber(cx, value)
673 }
674
675 fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
677 let list_string = self
678 .upcast::<Element>()
679 .get_string_attribute(&local_name!("list"));
680 if list_string.is_empty() {
681 return None;
682 }
683 let ancestor = self
684 .upcast::<Node>()
685 .GetRootNode(&GetRootNodeOptions::empty());
686 let first_with_id = &ancestor
687 .traverse_preorder(ShadowIncluding::No)
688 .find(|node| {
689 node.downcast::<Element>()
690 .is_some_and(|e| e.Id() == list_string)
691 });
692 first_with_id
693 .as_ref()
694 .and_then(|el| el.downcast::<HTMLDataListElement>())
695 .map(DomRoot::from_ref)
696 }
697
698 fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
700 self.input_type()
701 .as_specific()
702 .suffers_from_being_missing(self, value)
703 }
704
705 fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
707 if value.is_empty() {
708 return false;
709 }
710
711 self.input_type()
712 .as_specific()
713 .suffers_from_type_mismatch(self, value)
714 }
715
716 fn suffers_from_pattern_mismatch(&self, value: &DOMString, can_gc: CanGc) -> bool {
718 let pattern_str = self.Pattern();
721 if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
722 return false;
723 }
724
725 let cx = GlobalScope::get_cx();
727 let _ac = enter_realm(self);
728 rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>());
729
730 if compile_pattern(cx, &pattern_str.str(), pattern.handle_mut(), can_gc) {
731 if self.Multiple() && self.does_multiple_apply() {
732 !split_commas(&value.str())
733 .all(|s| matches_js_regex(cx, pattern.handle(), s, can_gc).unwrap_or(true))
734 } else {
735 !matches_js_regex(cx, pattern.handle(), &value.str(), can_gc).unwrap_or(true)
736 }
737 } else {
738 false
740 }
741 }
742
743 fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
745 if value.is_empty() {
746 return false;
747 }
748
749 self.input_type()
750 .as_specific()
751 .suffers_from_bad_input(value)
752 }
753
754 fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
757 let value_dirty = self.value_dirty.get();
760 let textinput = self.textinput.borrow();
761 let edit_by_user = !textinput.was_last_change_by_set_content();
762
763 if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
764 return ValidationFlags::empty();
765 }
766
767 let mut failed_flags = ValidationFlags::empty();
768 let Utf16CodeUnitLength(value_len) = textinput.len_utf16();
769 let min_length = self.MinLength();
770 let max_length = self.MaxLength();
771
772 if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
773 failed_flags.insert(ValidationFlags::TOO_SHORT);
774 }
775
776 if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
777 failed_flags.insert(ValidationFlags::TOO_LONG);
778 }
779
780 failed_flags
781 }
782
783 fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
787 if value.is_empty() || !self.does_value_as_number_apply() {
788 return ValidationFlags::empty();
789 }
790
791 let Some(value_as_number) = self.convert_string_to_number(&value.str()) else {
792 return ValidationFlags::empty();
793 };
794
795 let mut failed_flags = ValidationFlags::empty();
796 let min_value = self.minimum();
797 let max_value = self.maximum();
798
799 let has_reversed_range = match (min_value, max_value) {
801 (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
802 _ => false,
803 };
804
805 if has_reversed_range {
806 if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
808 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
809 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
810 }
811 } else {
812 if let Some(min_value) = min_value &&
814 value_as_number < min_value
815 {
816 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
817 }
818 if let Some(max_value) = max_value &&
820 value_as_number > max_value
821 {
822 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
823 }
824 }
825
826 if let Some(step) = self.allowed_value_step() {
828 let diff = (self.step_base() - value_as_number) % step / value_as_number;
832 if diff.abs() > 1e-12 {
833 failed_flags.insert(ValidationFlags::STEP_MISMATCH);
834 }
835 }
836
837 failed_flags
838 }
839
840 pub(crate) fn is_textual_or_password(&self) -> bool {
842 self.is_textual_or_password.get()
843 }
844
845 fn may_have_embedder_control(&self) -> bool {
846 let el = self.upcast::<Element>();
847 matches!(*self.input_type(), InputType::Color(_)) && !el.disabled_state()
848 }
849
850 fn handle_key_reaction(
851 &self,
852 cx: &mut js::context::JSContext,
853 action: KeyReaction,
854 event: &Event,
855 ) {
856 match action {
857 KeyReaction::TriggerDefaultAction => {
858 self.implicit_submission(cx);
859 event.mark_as_handled();
860 },
861 KeyReaction::DispatchInput(text, is_composing, input_type) => {
862 if event.IsTrusted() {
863 self.textinput.borrow().queue_input_event(
864 self.upcast(),
865 text,
866 is_composing,
867 input_type,
868 );
869 }
870 self.value_dirty.set(true);
871 self.update_placeholder_shown_state();
872 self.upcast::<Node>().dirty(NodeDamage::Other);
873 event.mark_as_handled();
874 },
875 KeyReaction::RedrawSelection => {
876 self.maybe_update_shared_selection();
877 event.mark_as_handled();
878 },
879 KeyReaction::Nothing => (),
880 }
881 }
882
883 fn value_for_shadow_dom(&self) -> DOMString {
885 let input_type = &*self.input_type();
886 match input_type {
887 InputType::Checkbox(_) |
888 InputType::Radio(_) |
889 InputType::Image(_) |
890 InputType::Hidden(_) |
891 InputType::Range(_) => input_type.as_specific().value_for_shadow_dom(self),
892 _ => {
893 if let Some(attribute_value) = self
894 .upcast::<Element>()
895 .get_attribute(&local_name!("value"))
896 .map(|attribute| attribute.Value())
897 {
898 return attribute_value;
899 }
900 input_type.as_specific().value_for_shadow_dom(self)
901 },
902 }
903 }
904
905 fn textinput_mut(&self) -> RefMut<'_, TextInput<EmbedderClipboardProvider>> {
906 self.textinput.borrow_mut()
907 }
908
909 fn schedule_a_selection_change_event(&self) {
911 if self.has_scheduled_selectionchange_event.get() {
913 return;
914 }
915 self.has_scheduled_selectionchange_event.set(true);
917 let this = Trusted::new(self);
919 self.owner_global()
920 .task_manager()
921 .user_interaction_task_source()
922 .queue(
923 task!(selectionchange_task_steps: move |cx| {
925 let this = this.root();
926 this.has_scheduled_selectionchange_event.set(false);
928 this.upcast::<EventTarget>().fire_event_with_params(cx,
930 atom!("selectionchange"),
931 EventBubbles::Bubbles,
932 EventCancelable::NotCancelable,
933 EventComposed::Composed,
934 );
935 }),
940 );
941 }
942}
943
944impl<'dom> LayoutDom<'dom, HTMLInputElement> {
945 pub(crate) fn size_for_layout(self) -> u32 {
955 self.unsafe_get().size.get()
956 }
957
958 pub(crate) fn selection_for_layout(self) -> Option<SharedSelection> {
959 if !self.unsafe_get().is_textual_or_password.get() {
960 return None;
961 }
962 Some(self.unsafe_get().shared_selection.clone())
963 }
964}
965
966impl TextControlElement for HTMLInputElement {
967 fn selection_api_applies(&self) -> bool {
969 matches!(
970 *self.input_type(),
971 InputType::Text(_) |
972 InputType::Search(_) |
973 InputType::Url(_) |
974 InputType::Tel(_) |
975 InputType::Password(_)
976 )
977 }
978
979 fn has_selectable_text(&self) -> bool {
987 self.is_textual_or_password() && !self.textinput.borrow().get_content().is_empty()
988 }
989
990 fn has_uncollapsed_selection(&self) -> bool {
991 self.textinput.borrow().has_uncollapsed_selection()
992 }
993
994 fn set_dirty_value_flag(&self, value: bool) {
995 self.value_dirty.set(value)
996 }
997
998 fn select_all(&self) {
999 self.textinput.borrow_mut().select_all();
1000 self.maybe_update_shared_selection();
1001 }
1002
1003 fn maybe_update_shared_selection(&self) {
1004 let offsets = self.textinput.borrow().sorted_selection_offsets_range();
1005 let (start, end) = (offsets.start.0, offsets.end.0);
1006 let range = TextByteRange::new(ByteIndex(start), ByteIndex(end));
1007 let enabled = self.is_textual_or_password() && self.upcast::<Element>().focus_state();
1008
1009 let mut shared_selection = self.shared_selection.borrow_mut();
1010 let range_remained_equal = range == shared_selection.range;
1011 if range_remained_equal && enabled == shared_selection.enabled {
1012 return;
1013 }
1014
1015 if !range_remained_equal {
1016 self.schedule_a_selection_change_event();
1021 }
1022
1023 *shared_selection = ScriptSelection {
1024 range,
1025 character_range: self
1026 .textinput
1027 .borrow()
1028 .sorted_selection_character_offsets_range(),
1029 enabled,
1030 };
1031 self.owner_window().layout().set_needs_new_display_list();
1032 }
1033
1034 fn is_password_field(&self) -> bool {
1035 matches!(*self.input_type(), InputType::Password(_))
1036 }
1037
1038 fn placeholder_text<'a>(&'a self) -> Ref<'a, DOMString> {
1039 self.placeholder.borrow()
1040 }
1041
1042 fn value_text(&self) -> DOMString {
1043 self.Value()
1044 }
1045}
1046
1047impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
1048 make_getter!(Accept, "accept");
1050
1051 make_setter!(SetAccept, "accept");
1053
1054 make_bool_getter!(Alpha, "alpha");
1056
1057 make_bool_setter!(SetAlpha, "alpha");
1059
1060 make_getter!(Alt, "alt");
1062
1063 make_setter!(SetAlt, "alt");
1065
1066 make_getter!(DirName, "dirname");
1068
1069 make_setter!(SetDirName, "dirname");
1071
1072 make_bool_getter!(Disabled, "disabled");
1074
1075 make_bool_setter!(SetDisabled, "disabled");
1077
1078 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1080 self.form_owner()
1081 }
1082
1083 fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1085 self.input_type()
1086 .as_specific()
1087 .get_files()
1088 .as_ref()
1089 .cloned()
1090 }
1091
1092 fn SetFiles(&self, _cx: &mut js::context::JSContext, files: Option<&FileList>) {
1094 if let Some(files) = files {
1095 self.input_type().as_specific().set_files(files)
1096 }
1097 }
1098
1099 make_bool_getter!(DefaultChecked, "checked");
1101
1102 make_bool_setter!(SetDefaultChecked, "checked");
1104
1105 fn Checked(&self) -> bool {
1107 self.upcast::<Element>()
1108 .state()
1109 .contains(ElementState::CHECKED)
1110 }
1111
1112 fn SetChecked(&self, cx: &mut JSContext, checked: bool) {
1114 self.update_checked_state(cx, checked, true);
1115 self.value_changed(cx);
1116 }
1117
1118 make_enumerated_getter!(
1120 ColorSpace,
1121 "colorspace",
1122 "limited-srgb" | "display-p3",
1123 missing => "limited-srgb",
1124 invalid => "limited-srgb"
1125 );
1126
1127 make_setter!(SetColorSpace, "colorspace");
1129
1130 make_bool_getter!(ReadOnly, "readonly");
1132
1133 make_bool_setter!(SetReadOnly, "readonly");
1135
1136 make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1138
1139 make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1141
1142 fn Type(&self) -> DOMString {
1144 DOMString::from(self.input_type().as_str())
1145 }
1146
1147 make_atomic_setter!(SetType, "type");
1149
1150 fn Value(&self) -> DOMString {
1152 match self.value_mode() {
1153 ValueMode::Value => self.textinput.borrow().get_content(),
1154 ValueMode::Default => self
1155 .upcast::<Element>()
1156 .get_attribute(&local_name!("value"))
1157 .map_or(DOMString::from(""), |a| {
1158 DOMString::from(a.summarize().value)
1159 }),
1160 ValueMode::DefaultOn => self
1161 .upcast::<Element>()
1162 .get_attribute(&local_name!("value"))
1163 .map_or(DOMString::from("on"), |a| {
1164 DOMString::from(a.summarize().value)
1165 }),
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(CanGc::from_cx(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) -> DOMString {
1580 self.validation_message()
1581 }
1582
1583 fn SetCustomValidity(&self, cx: &mut JSContext, error: DOMString) {
1585 self.validity_state(CanGc::from_cx(cx))
1586 .set_custom_error_message(error);
1587 }
1588}
1589
1590impl HTMLInputElement {
1591 pub(crate) fn form_datums(
1594 &self,
1595 submitter: Option<FormSubmitterElement>,
1596 encoding: Option<&'static Encoding>,
1597 ) -> Vec<FormDatum> {
1598 let ty = self.Type();
1602
1603 let name = self.Name();
1605 let is_submitter = match submitter {
1606 Some(FormSubmitterElement::Input(s)) => self == s,
1607 _ => false,
1608 };
1609
1610 match *self.input_type() {
1611 InputType::Submit(_) | InputType::Button(_) | InputType::Reset(_) if !is_submitter => {
1613 return vec![];
1614 },
1615
1616 InputType::Radio(_) | InputType::Checkbox(_) => {
1618 if !self.Checked() || name.is_empty() {
1619 return vec![];
1620 }
1621 },
1622
1623 InputType::File(_) => {
1624 let mut datums = vec![];
1625
1626 let name = self.Name();
1628
1629 match self.GetFiles() {
1630 Some(fl) => {
1631 for f in fl.iter_files() {
1632 datums.push(FormDatum {
1633 ty: ty.clone(),
1634 name: name.clone(),
1635 value: FormDatumValue::File(DomRoot::from_ref(f)),
1636 });
1637 }
1638 },
1639 None => {
1640 datums.push(FormDatum {
1641 ty,
1644 name,
1645 value: FormDatumValue::String(DOMString::from("")),
1646 })
1647 },
1648 }
1649
1650 return datums;
1651 },
1652
1653 InputType::Image(_) => return vec![], InputType::Hidden(_) => {
1657 if name.to_ascii_lowercase() == "_charset_" {
1658 return vec![FormDatum {
1659 ty,
1660 name,
1661 value: FormDatumValue::String(match encoding {
1662 None => DOMString::from("UTF-8"),
1663 Some(enc) => DOMString::from(enc.name()),
1664 }),
1665 }];
1666 }
1667 },
1668
1669 _ => {
1671 if name.is_empty() {
1672 return vec![];
1673 }
1674 },
1675 }
1676
1677 vec![FormDatum {
1679 ty,
1680 name,
1681 value: FormDatumValue::String(self.Value()),
1682 }]
1683 }
1684
1685 fn radio_group_name(&self) -> Option<Atom> {
1687 self.upcast::<Element>()
1688 .get_name()
1689 .filter(|name| !name.is_empty())
1690 }
1691
1692 fn update_checked_state(&self, cx: &mut JSContext, checked: bool, dirty: bool) {
1693 self.upcast::<Element>()
1694 .set_state(ElementState::CHECKED, checked);
1695
1696 if dirty {
1697 self.checked_changed.set(true);
1698 }
1699
1700 if matches!(*self.input_type(), InputType::Radio(_)) && checked {
1701 broadcast_radio_checked(cx, self, self.radio_group_name().as_ref());
1702 }
1703
1704 self.upcast::<Node>().dirty(NodeDamage::Other);
1705 }
1706
1707 pub(crate) fn is_mutable(&self) -> bool {
1709 !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
1712 }
1713
1714 pub(crate) fn reset(&self, cx: &mut JSContext) {
1724 self.value_dirty.set(false);
1725
1726 let mut value = self.DefaultValue();
1728 self.sanitize_value(&mut value);
1729 self.textinput.borrow_mut().set_content(value);
1730
1731 let input_type = &*self.input_type();
1732 if matches!(input_type, InputType::Radio(_) | InputType::Checkbox(_)) {
1733 self.update_checked_state(cx, self.DefaultChecked(), false);
1734 self.checked_changed.set(false);
1735 }
1736
1737 if matches!(input_type, InputType::File(_)) {
1738 input_type.as_specific().set_files(&FileList::new(
1739 &self.owner_window(),
1740 vec![],
1741 CanGc::from_cx(cx),
1742 ));
1743 }
1744
1745 self.value_changed(cx);
1746 }
1747
1748 pub(crate) fn clear(&self, cx: &mut JSContext) {
1751 self.value_dirty.set(false);
1753 self.checked_changed.set(false);
1754 self.textinput.borrow_mut().set_content(DOMString::from(""));
1756 self.update_checked_state(cx, self.DefaultChecked(), false);
1758 if self.input_type().as_specific().get_files().is_some() {
1760 let window = self.owner_window();
1761 let filelist = FileList::new(&window, vec![], CanGc::from_cx(cx));
1762 self.input_type().as_specific().set_files(&filelist);
1763 }
1764
1765 {
1768 let mut textinput = self.textinput.borrow_mut();
1769 let mut value = textinput.get_content();
1770 self.sanitize_value(&mut value);
1771 textinput.set_content(value);
1772 }
1773
1774 self.value_changed(cx);
1775 }
1776
1777 fn update_placeholder_shown_state(&self) {
1778 if !self.input_type().is_textual_or_password() {
1779 self.upcast::<Element>().set_placeholder_shown_state(false);
1780 } else {
1781 let has_placeholder = !self.placeholder.borrow().is_empty();
1782 let has_value = !self.textinput.borrow().is_empty();
1783 self.upcast::<Element>()
1784 .set_placeholder_shown_state(has_placeholder && !has_value);
1785 }
1786 }
1787
1788 pub(crate) fn select_files_for_webdriver(
1789 &self,
1790 test_paths: Vec<DOMString>,
1791 response_sender: GenericSender<Result<bool, ErrorStatus>>,
1792 ) {
1793 let mut stored_sender = self.pending_webdriver_response.borrow_mut();
1794 assert!(stored_sender.is_none());
1795
1796 *stored_sender = Some(PendingWebDriverResponse {
1797 response_sender,
1798 expected_file_count: test_paths.len(),
1799 });
1800
1801 self.input_type()
1802 .as_specific()
1803 .select_files(self, Some(test_paths));
1804 }
1805
1806 fn sanitize_value(&self, value: &mut DOMString) {
1808 self.input_type().as_specific().sanitize_value(self, value);
1809 }
1810
1811 fn selection(&self) -> TextControlSelection<'_, Self> {
1812 TextControlSelection::new(self, &self.textinput)
1813 }
1814
1815 fn implicit_submission(&self, cx: &mut js::context::JSContext) {
1817 let doc = self.owner_document();
1818 let node = doc.upcast::<Node>();
1819 let owner = self.form_owner();
1820 let form = match owner {
1821 None => return,
1822 Some(ref f) => f,
1823 };
1824
1825 if self.upcast::<Element>().click_in_progress() {
1826 return;
1827 }
1828 let submit_button = node
1829 .traverse_preorder(ShadowIncluding::No)
1830 .filter_map(DomRoot::downcast::<HTMLInputElement>)
1831 .filter(|input| matches!(*input.input_type(), InputType::Submit(_)))
1832 .find(|r| r.form_owner() == owner);
1833 match submit_button {
1834 Some(ref button) => {
1835 if button.is_instance_activatable() {
1836 button
1839 .upcast::<Node>()
1840 .fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
1841 }
1842 },
1843 None => {
1844 let mut inputs = node
1845 .traverse_preorder(ShadowIncluding::No)
1846 .filter_map(DomRoot::downcast::<HTMLInputElement>)
1847 .filter(|input| {
1848 input.form_owner() == owner &&
1849 matches!(
1850 *input.input_type(),
1851 InputType::Text(_) |
1852 InputType::Search(_) |
1853 InputType::Url(_) |
1854 InputType::Tel(_) |
1855 InputType::Email(_) |
1856 InputType::Password(_) |
1857 InputType::Date(_) |
1858 InputType::Month(_) |
1859 InputType::Week(_) |
1860 InputType::Time(_) |
1861 InputType::DatetimeLocal(_) |
1862 InputType::Number(_)
1863 )
1864 });
1865
1866 if inputs.nth(1).is_some() {
1867 return;
1869 }
1870 form.submit(
1871 cx,
1872 SubmittedFrom::NotFromForm,
1873 FormSubmitterElement::Form(form),
1874 );
1875 },
1876 }
1877 }
1878
1879 fn convert_string_to_number(&self, value: &str) -> Option<f64> {
1881 self.input_type()
1882 .as_specific()
1883 .convert_string_to_number(value)
1884 }
1885
1886 fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
1888 self.input_type()
1889 .as_specific()
1890 .convert_number_to_string(value)
1891 }
1892
1893 fn update_related_validity_states(&self, cx: &mut JSContext) {
1894 match *self.input_type() {
1895 InputType::Radio(_) => {
1896 perform_radio_group_validation(cx, self, self.radio_group_name().as_ref())
1897 },
1898 _ => {
1899 self.validity_state(CanGc::from_cx(cx))
1900 .perform_validation_and_update(ValidationFlags::all(), CanGc::from_cx(cx));
1901 },
1902 }
1903 }
1904
1905 fn value_changed(&self, cx: &mut JSContext) {
1906 self.maybe_update_shared_selection();
1907 self.update_related_validity_states(cx);
1908 self.input_type().as_specific().update_shadow_tree(cx, self);
1909 }
1910
1911 fn show_the_picker_if_applicable(&self) {
1913 if !self.is_mutable() {
1917 return;
1918 }
1919
1920 self.input_type()
1923 .as_specific()
1924 .show_the_picker_if_applicable(self);
1925 }
1926
1927 pub(crate) fn handle_color_picker_response(
1928 &self,
1929 cx: &mut js::context::JSContext,
1930 response: Option<RgbColor>,
1931 ) {
1932 if let InputType::Color(ref color_input_type) = *self.input_type() {
1933 color_input_type.handle_color_picker_response(cx, self, response)
1934 }
1935 }
1936
1937 pub(crate) fn handle_file_picker_response(
1938 &self,
1939 cx: &mut js::context::JSContext,
1940 response: Option<Vec<SelectedFile>>,
1941 ) {
1942 if let InputType::File(ref file_input_type) = *self.input_type() {
1943 file_input_type.handle_file_picker_response(cx, self, response)
1944 }
1945 }
1946
1947 fn handle_focus_event(&self, event: &FocusEvent) {
1948 let event_type = event.upcast::<Event>().type_();
1949 if *event_type == *"blur" {
1950 self.owner_document()
1951 .embedder_controls()
1952 .hide_embedder_control(self.upcast());
1953 } else if *event_type == *"focus" {
1954 let input_type = &*self.input_type();
1955 let Ok(input_method_type) = input_type.try_into() else {
1956 return;
1957 };
1958
1959 self.owner_document()
1960 .embedder_controls()
1961 .show_embedder_control(
1962 ControlElement::Ime(DomRoot::from_ref(self.upcast())),
1963 EmbedderControlRequest::InputMethod(InputMethodRequest {
1964 input_method_type,
1965 text: self.Value().to_string(),
1966 insertion_point: self.GetSelectionEnd(),
1967 multiline: false,
1968 allow_virtual_keyboard: self.owner_window().has_sticky_activation(),
1970 }),
1971 None,
1972 );
1973 }
1974 }
1975
1976 fn handle_mouse_event(&self, mouse_event: &MouseEvent) {
1977 if mouse_event.upcast::<Event>().DefaultPrevented() {
1978 return;
1979 }
1980
1981 if !self.input_type().is_textual_or_password() || self.textinput.borrow().is_empty() {
1984 return;
1985 }
1986 let node = self.upcast();
1987 if self
1988 .textinput
1989 .borrow_mut()
1990 .handle_mouse_event(node, mouse_event)
1991 {
1992 self.maybe_update_shared_selection();
1993 }
1994 }
1995}
1996
1997impl VirtualMethods for HTMLInputElement {
1998 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1999 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
2000 }
2001
2002 fn attribute_mutated(
2003 &self,
2004 cx: &mut JSContext,
2005 attr: AttrRef<'_>,
2006 mutation: AttributeMutation,
2007 ) {
2008 let could_have_had_embedder_control = self.may_have_embedder_control();
2009
2010 self.super_type()
2011 .unwrap()
2012 .attribute_mutated(cx, attr, mutation);
2013
2014 match *attr.local_name() {
2015 local_name!("disabled") => {
2016 let disabled_state = match mutation {
2017 AttributeMutation::Set(None, _) => true,
2018 AttributeMutation::Set(Some(_), _) => {
2019 return;
2021 },
2022 AttributeMutation::Removed => false,
2023 };
2024 let el = self.upcast::<Element>();
2025 el.set_disabled_state(disabled_state);
2026 el.set_enabled_state(!disabled_state);
2027 el.check_ancestors_disabled_state_for_form_control();
2028
2029 if self.input_type().is_textual() {
2030 let read_write = !(self.ReadOnly() || el.disabled_state());
2031 el.set_read_write_state(read_write);
2032 }
2033 },
2034 local_name!("checked") if !self.checked_changed.get() => {
2035 let checked_state = match mutation {
2036 AttributeMutation::Set(None, _) => true,
2037 AttributeMutation::Set(Some(_), _) => {
2038 return;
2040 },
2041 AttributeMutation::Removed => false,
2042 };
2043 self.update_checked_state(cx, checked_state, false);
2044 },
2045 local_name!("size") => {
2046 let size = mutation.new_value(attr).map(|value| value.as_uint());
2047 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
2048 },
2049 local_name!("type") => {
2050 match mutation {
2051 AttributeMutation::Set(..) => {
2052 let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
2054 let previously_selectable = self.selection_api_applies();
2055
2056 *self.input_type.borrow_mut() =
2057 InputType::new_from_atom(attr.value().as_atom());
2058 self.is_textual_or_password
2059 .set(self.input_type().is_textual_or_password());
2060
2061 let element = self.upcast::<Element>();
2062 if self.input_type().is_textual() {
2063 let read_write = !(self.ReadOnly() || element.disabled_state());
2064 element.set_read_write_state(read_write);
2065 } else {
2066 element.set_read_write_state(false);
2067 }
2068
2069 let new_value_mode = self.value_mode();
2070 match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
2071 (&ValueMode::Value, false, ValueMode::Default) |
2073 (&ValueMode::Value, false, ValueMode::DefaultOn) => {
2074 self.SetValue(cx, old_idl_value)
2075 .expect("Failed to set input value on type change to a default ValueMode.");
2076 },
2077
2078 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
2080 self.SetValue(
2081 cx,
2082 self.upcast::<Element>()
2083 .get_attribute(&local_name!("value"))
2084 .map_or(DOMString::from(""), |a| {
2085 DOMString::from(a.summarize().value)
2086 }),
2087 )
2088 .expect(
2089 "Failed to set input value on type change to ValueMode::Value.",
2090 );
2091 self.value_dirty.set(false);
2092 },
2093
2094 (_, _, ValueMode::Filename)
2096 if old_value_mode != ValueMode::Filename =>
2097 {
2098 self.SetValue(cx, DOMString::from(""))
2099 .expect("Failed to set input value on type change to ValueMode::Filename.");
2100 },
2101 _ => {},
2102 }
2103
2104 self.input_type().as_specific().signal_type_change(cx, self);
2106
2107 let mut textinput = self.textinput.borrow_mut();
2109 let mut value = textinput.get_content();
2110 self.sanitize_value(&mut value);
2111 textinput.set_content(value);
2112 self.upcast::<Node>().dirty(NodeDamage::Other);
2113
2114 if !previously_selectable && self.selection_api_applies() {
2116 textinput.clear_selection_to_start();
2117 }
2118 },
2119 AttributeMutation::Removed => {
2120 self.input_type().as_specific().signal_type_change(cx, self);
2121 *self.input_type.borrow_mut() = InputType::new_text();
2122 self.is_textual_or_password
2123 .set(self.input_type().is_textual_or_password());
2124
2125 let element = self.upcast::<Element>();
2126 let read_write = !(self.ReadOnly() || element.disabled_state());
2127 element.set_read_write_state(read_write);
2128 },
2129 }
2130
2131 self.update_placeholder_shown_state();
2132 self.input_type()
2133 .as_specific()
2134 .update_placeholder_contents(cx, self);
2135 },
2136 local_name!("value") if !self.value_dirty.get() => {
2137 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
2141 let mut value = value.map_or(DOMString::new(), DOMString::from);
2142
2143 self.sanitize_value(&mut value);
2144 self.textinput.borrow_mut().set_content(value);
2145 self.update_placeholder_shown_state();
2146 },
2147 local_name!("maxlength") => match *attr.value() {
2148 AttrValue::Int(_, value) => {
2149 let mut textinput = self.textinput.borrow_mut();
2150
2151 if value < 0 {
2152 textinput.set_max_length(None);
2153 } else {
2154 textinput.set_max_length(Some(Utf16CodeUnitLength(value as usize)))
2155 }
2156 },
2157 _ => panic!("Expected an AttrValue::Int"),
2158 },
2159 local_name!("minlength") => match *attr.value() {
2160 AttrValue::Int(_, value) => {
2161 let mut textinput = self.textinput.borrow_mut();
2162
2163 if value < 0 {
2164 textinput.set_min_length(None);
2165 } else {
2166 textinput.set_min_length(Some(Utf16CodeUnitLength(value as usize)))
2167 }
2168 },
2169 _ => panic!("Expected an AttrValue::Int"),
2170 },
2171 local_name!("placeholder") => {
2172 {
2173 let mut placeholder = self.placeholder.borrow_mut();
2174 placeholder.clear();
2175 if let AttributeMutation::Set(..) = mutation {
2176 placeholder
2177 .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
2178 }
2179 }
2180 self.update_placeholder_shown_state();
2181 self.input_type()
2182 .as_specific()
2183 .update_placeholder_contents(cx, self);
2184 },
2185 local_name!("readonly") => {
2186 if self.input_type().is_textual() {
2187 let el = self.upcast::<Element>();
2188 match mutation {
2189 AttributeMutation::Set(..) => {
2190 el.set_read_write_state(false);
2191 },
2192 AttributeMutation::Removed => {
2193 el.set_read_write_state(!el.disabled_state());
2194 },
2195 }
2196 }
2197 },
2198 local_name!("form") => {
2199 self.form_attribute_mutated(mutation, CanGc::from_cx(cx));
2200 },
2201 _ => {
2202 self.input_type()
2203 .as_specific()
2204 .attribute_mutated(cx, self, attr, mutation);
2205 },
2206 }
2207
2208 self.value_changed(cx);
2209
2210 if could_have_had_embedder_control && !self.may_have_embedder_control() {
2211 self.owner_document()
2212 .embedder_controls()
2213 .hide_embedder_control(self.upcast());
2214 }
2215 }
2216
2217 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
2218 match *name {
2219 local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
2220 local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
2221 local_name!("type") => AttrValue::from_atomic(value.into()),
2222 local_name!("maxlength") => {
2223 AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
2224 },
2225 local_name!("minlength") => {
2226 AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
2227 },
2228 _ => self
2229 .super_type()
2230 .unwrap()
2231 .parse_plain_attribute(name, value),
2232 }
2233 }
2234
2235 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
2236 if let Some(s) = self.super_type() {
2237 s.bind_to_tree(cx, context);
2238 }
2239 self.upcast::<Element>()
2240 .check_ancestors_disabled_state_for_form_control();
2241
2242 self.input_type()
2243 .as_specific()
2244 .bind_to_tree(cx, self, context);
2245
2246 self.value_changed(cx);
2247 }
2248
2249 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
2250 let form_owner = self.form_owner();
2251 self.super_type().unwrap().unbind_from_tree(cx, context);
2252
2253 let node = self.upcast::<Node>();
2254 let el = self.upcast::<Element>();
2255 if node
2256 .ancestors()
2257 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
2258 {
2259 el.check_ancestors_disabled_state_for_form_control();
2260 } else {
2261 el.check_disabled_attribute();
2262 }
2263
2264 self.input_type().as_specific().unbind_from_tree(
2265 self,
2266 form_owner,
2267 context,
2268 CanGc::from_cx(cx),
2269 );
2270
2271 self.validity_state(CanGc::from_cx(cx))
2272 .perform_validation_and_update(ValidationFlags::all(), CanGc::from_cx(cx));
2273 }
2274
2275 fn handle_event(&self, cx: &mut JSContext, event: &Event) {
2281 if let Some(mouse_event) = event.downcast::<MouseEvent>() {
2282 self.handle_mouse_event(mouse_event);
2283 event.mark_as_handled();
2284 } else if event.type_() == atom!("keydown") &&
2285 !event.DefaultPrevented() &&
2286 self.input_type().is_textual_or_password()
2287 {
2288 if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
2289 let action = self.textinput.borrow_mut().handle_keydown(keyevent);
2292 self.handle_key_reaction(cx, action, event);
2293 }
2294 } else if (event.type_() == atom!("compositionstart") ||
2295 event.type_() == atom!("compositionupdate") ||
2296 event.type_() == atom!("compositionend")) &&
2297 self.input_type().is_textual_or_password()
2298 {
2299 if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
2300 if event.type_() == atom!("compositionend") {
2301 let action = self
2302 .textinput
2303 .borrow_mut()
2304 .handle_compositionend(compositionevent);
2305 self.handle_key_reaction(cx, action, event);
2306 self.upcast::<Node>().dirty(NodeDamage::Other);
2307 self.update_placeholder_shown_state();
2308 } else if event.type_() == atom!("compositionupdate") {
2309 let action = self
2310 .textinput
2311 .borrow_mut()
2312 .handle_compositionupdate(compositionevent);
2313 self.handle_key_reaction(cx, action, event);
2314 self.upcast::<Node>().dirty(NodeDamage::Other);
2315 self.update_placeholder_shown_state();
2316 } else if event.type_() == atom!("compositionstart") {
2317 self.update_placeholder_shown_state();
2319 }
2320 event.mark_as_handled();
2321 }
2322 } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
2323 let reaction = self
2324 .textinput
2325 .borrow_mut()
2326 .handle_clipboard_event(clipboard_event);
2327 let flags = reaction.flags;
2328 if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
2329 self.owner_document().event_handler().fire_clipboard_event(
2330 cx,
2331 None,
2332 ClipboardEventType::Change,
2333 );
2334 }
2335 if flags.contains(ClipboardEventFlags::QueueInputEvent) {
2336 self.textinput.borrow().queue_input_event(
2337 self.upcast(),
2338 reaction.text,
2339 IsComposing::NotComposing,
2340 reaction.input_type,
2341 );
2342 }
2343 if !flags.is_empty() {
2344 event.mark_as_handled();
2345 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
2346 }
2347 } else if let Some(event) = event.downcast::<FocusEvent>() {
2348 self.handle_focus_event(event)
2349 }
2350
2351 self.value_changed(cx);
2352
2353 if let Some(super_type) = self.super_type() {
2354 super_type.handle_event(cx, event);
2355 }
2356 }
2357
2358 fn cloning_steps(
2360 &self,
2361 cx: &mut JSContext,
2362 copy: &Node,
2363 maybe_doc: Option<&Document>,
2364 clone_children: CloneChildrenFlag,
2365 ) {
2366 if let Some(s) = self.super_type() {
2367 s.cloning_steps(cx, copy, maybe_doc, clone_children);
2368 }
2369 let elem = copy.downcast::<HTMLInputElement>().unwrap();
2370 elem.value_dirty.set(self.value_dirty.get());
2371 elem.checked_changed.set(self.checked_changed.get());
2372 elem.upcast::<Element>()
2373 .set_state(ElementState::CHECKED, self.Checked());
2374 elem.textinput
2375 .borrow_mut()
2376 .set_content(self.textinput.borrow().get_content());
2377 self.value_changed(cx);
2378 }
2379}
2380
2381impl FormControl for HTMLInputElement {
2382 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
2383 self.form_owner.get()
2384 }
2385
2386 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
2387 self.form_owner.set(form);
2388 }
2389
2390 fn to_element(&self) -> &Element {
2391 self.upcast::<Element>()
2392 }
2393}
2394
2395impl Validatable for HTMLInputElement {
2396 fn as_element(&self) -> &Element {
2397 self.upcast()
2398 }
2399
2400 fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
2401 self.validity_state
2402 .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
2403 }
2404
2405 fn is_instance_validatable(&self) -> bool {
2406 match *self.input_type() {
2413 InputType::Hidden(_) | InputType::Button(_) | InputType::Reset(_) => false,
2414 _ => {
2415 !(self.upcast::<Element>().disabled_state() ||
2416 self.ReadOnly() ||
2417 is_barred_by_datalist_ancestor(self.upcast()))
2418 },
2419 }
2420 }
2421
2422 fn perform_validation(
2423 &self,
2424 validate_flags: ValidationFlags,
2425 can_gc: CanGc,
2426 ) -> ValidationFlags {
2427 let mut failed_flags = ValidationFlags::empty();
2428 let value = self.Value();
2429
2430 if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
2431 self.suffers_from_being_missing(&value)
2432 {
2433 failed_flags.insert(ValidationFlags::VALUE_MISSING);
2434 }
2435
2436 if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
2437 self.suffers_from_type_mismatch(&value)
2438 {
2439 failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
2440 }
2441
2442 if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
2443 self.suffers_from_pattern_mismatch(&value, can_gc)
2444 {
2445 failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
2446 }
2447
2448 if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
2449 self.suffers_from_bad_input(&value)
2450 {
2451 failed_flags.insert(ValidationFlags::BAD_INPUT);
2452 }
2453
2454 if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
2455 failed_flags |= self.suffers_from_length_issues(&value);
2456 }
2457
2458 if validate_flags.intersects(
2459 ValidationFlags::RANGE_UNDERFLOW |
2460 ValidationFlags::RANGE_OVERFLOW |
2461 ValidationFlags::STEP_MISMATCH,
2462 ) {
2463 failed_flags |= self.suffers_from_range_issues(&value);
2464 }
2465
2466 failed_flags & validate_flags
2467 }
2468}
2469
2470impl Activatable for HTMLInputElement {
2471 fn as_element(&self) -> &Element {
2472 self.upcast()
2473 }
2474
2475 fn is_instance_activatable(&self) -> bool {
2476 match *self.input_type() {
2477 InputType::Submit(_) |
2484 InputType::Reset(_) |
2485 InputType::File(_) |
2486 InputType::Image(_) |
2487 InputType::Button(_) => self.is_mutable(),
2488 InputType::Checkbox(_) | InputType::Radio(_) | InputType::Color(_) => true,
2492 _ => false,
2493 }
2494 }
2495
2496 fn legacy_pre_activation_behavior(&self, cx: &mut JSContext) -> Option<InputActivationState> {
2498 let activation_state = self
2499 .input_type()
2500 .as_specific()
2501 .legacy_pre_activation_behavior(cx, self);
2502
2503 if activation_state.is_some() {
2504 self.value_changed(cx);
2505 }
2506
2507 activation_state
2508 }
2509
2510 fn legacy_canceled_activation_behavior(
2512 &self,
2513 cx: &mut JSContext,
2514 cache: Option<InputActivationState>,
2515 ) {
2516 let ty = self.input_type();
2518 let cache = match cache {
2519 Some(cache) => {
2520 if (cache.was_radio && !matches!(*ty, InputType::Radio(_))) ||
2521 (cache.was_checkbox && !matches!(*ty, InputType::Checkbox(_)))
2522 {
2523 return;
2526 }
2527 cache
2528 },
2529 None => {
2530 return;
2531 },
2532 };
2533
2534 ty.as_specific()
2536 .legacy_canceled_activation_behavior(cx, self, cache);
2537
2538 self.value_changed(cx);
2539 }
2540
2541 fn activation_behavior(
2543 &self,
2544 cx: &mut js::context::JSContext,
2545 event: &Event,
2546 target: &EventTarget,
2547 ) {
2548 self.input_type()
2549 .as_specific()
2550 .activation_behavior(cx, self, event, target);
2551 }
2552}
2553
2554fn compile_pattern(
2558 cx: SafeJSContext,
2559 pattern_str: &str,
2560 out_regex: MutableHandleObject,
2561 can_gc: CanGc,
2562) -> bool {
2563 if check_js_regex_syntax(cx, pattern_str, can_gc) {
2565 let pattern_str = format!("^(?:{})$", pattern_str);
2567 let flags = RegExpFlags {
2568 flags_: RegExpFlag_UnicodeSets,
2569 };
2570 new_js_regex(cx, &pattern_str, flags, out_regex, can_gc)
2571 } else {
2572 false
2573 }
2574}
2575
2576#[expect(unsafe_code)]
2577fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str, _can_gc: CanGc) -> bool {
2580 let pattern: Vec<u16> = pattern.encode_utf16().collect();
2581 unsafe {
2582 rooted!(in(*cx) let mut exception = UndefinedValue());
2583
2584 let valid = CheckRegExpSyntax(
2585 *cx,
2586 pattern.as_ptr(),
2587 pattern.len(),
2588 RegExpFlags {
2589 flags_: RegExpFlag_UnicodeSets,
2590 },
2591 exception.handle_mut(),
2592 );
2593
2594 if !valid {
2595 JS_ClearPendingException(*cx);
2596 return false;
2597 }
2598
2599 exception.is_undefined()
2602 }
2603}
2604
2605#[expect(unsafe_code)]
2606pub(crate) fn new_js_regex(
2607 cx: SafeJSContext,
2608 pattern: &str,
2609 flags: RegExpFlags,
2610 mut out_regex: MutableHandleObject,
2611 _can_gc: CanGc,
2612) -> bool {
2613 let pattern: Vec<u16> = pattern.encode_utf16().collect();
2614 unsafe {
2615 out_regex.set(NewUCRegExpObject(
2616 *cx,
2617 pattern.as_ptr(),
2618 pattern.len(),
2619 flags,
2620 ));
2621 if out_regex.is_null() {
2622 JS_ClearPendingException(*cx);
2623 return false;
2624 }
2625 }
2626 true
2627}
2628
2629#[expect(unsafe_code)]
2630fn matches_js_regex(
2631 cx: SafeJSContext,
2632 regex_obj: HandleObject,
2633 value: &str,
2634 _can_gc: CanGc,
2635) -> Result<bool, ()> {
2636 let mut value: Vec<u16> = value.encode_utf16().collect();
2637
2638 unsafe {
2639 let mut is_regex = false;
2640 assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
2641 assert!(is_regex);
2642
2643 rooted!(in(*cx) let mut rval = UndefinedValue());
2644 let mut index = 0;
2645
2646 let ok = ExecuteRegExpNoStatics(
2647 *cx,
2648 regex_obj,
2649 value.as_mut_ptr(),
2650 value.len(),
2651 &mut index,
2652 true,
2653 rval.handle_mut(),
2654 );
2655
2656 if ok {
2657 Ok(!rval.is_null())
2658 } else {
2659 JS_ClearPendingException(*cx);
2660 Err(())
2661 }
2662 }
2663}
2664
2665#[derive(MallocSizeOf)]
2669struct PendingWebDriverResponse {
2670 response_sender: GenericSender<Result<bool, ErrorStatus>>,
2672 expected_file_count: usize,
2674}
2675
2676impl PendingWebDriverResponse {
2677 fn finish(self, number_files_selected: usize) {
2678 if number_files_selected == self.expected_file_count {
2679 let _ = self.response_sender.send(Ok(false));
2680 } else {
2681 let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
2684 }
2685 }
2686}