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, DateGetMsecSinceEpoch, Handle, JS_ClearPendingException, JSObject, NewDateObject,
17 NewUCRegExpObject, ObjectIsDate, RegExpFlag_UnicodeSets, RegExpFlags,
18};
19use js::jsval::UndefinedValue;
20use js::rust::wrappers::{CheckRegExpSyntax, ExecuteRegExpNoStatics, ObjectIsRegExp};
21use js::rust::{HandleObject, MutableHandleObject};
22use layout_api::{ScriptSelection, SharedSelection};
23use script_bindings::codegen::GenericBindings::AttrBinding::AttrMethods;
24use script_bindings::domstring::parse_floating_point_number;
25use servo_base::generic_channel::GenericSender;
26use servo_base::text::Utf16CodeUnitLength;
27use style::attr::AttrValue;
28use style::str::split_commas;
29use stylo_atoms::Atom;
30use stylo_dom::ElementState;
31use time::OffsetDateTime;
32use unicode_bidi::{BidiClass, bidi_class};
33use webdriver::error::ErrorStatus;
34
35use crate::clipboard_provider::EmbedderClipboardProvider;
36use crate::dom::activation::Activatable;
37use crate::dom::attr::Attr;
38use crate::dom::bindings::cell::{DomRefCell, Ref};
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::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::{AttributeMutation, Element};
54use crate::dom::event::Event;
55use crate::dom::eventtarget::EventTarget;
56use crate::dom::filelist::FileList;
57use crate::dom::globalscope::GlobalScope;
58use crate::dom::html::htmldatalistelement::HTMLDataListElement;
59use crate::dom::html::htmlelement::HTMLElement;
60use crate::dom::html::htmlfieldsetelement::HTMLFieldSetElement;
61use crate::dom::html::htmlformelement::{
62 FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, SubmittedFrom,
63};
64use crate::dom::htmlinputelement::radio_input_type::{
65 broadcast_radio_checked, perform_radio_group_validation,
66};
67use crate::dom::input_element::input_type::InputType;
68use crate::dom::keyboardevent::KeyboardEvent;
69use crate::dom::node::{
70 BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext,
71};
72use crate::dom::nodelist::NodeList;
73use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
74use crate::dom::types::{FocusEvent, MouseEvent};
75use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
76use crate::dom::validitystate::{ValidationFlags, ValidityState};
77use crate::dom::virtualmethods::VirtualMethods;
78use crate::realms::enter_realm;
79use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
80use crate::textinput::{ClipboardEventFlags, IsComposing, KeyReaction, Lines, TextInput};
81
82pub(crate) mod button_input_type;
83pub(crate) mod checkbox_input_type;
84pub(crate) mod color_input_type;
85pub(crate) mod date_input_type;
86pub(crate) mod datetime_local_input_type;
87pub(crate) mod email_input_type;
88pub(crate) mod file_input_type;
89pub(crate) mod hidden_input_type;
90pub(crate) mod image_input_type;
91pub(crate) mod input_type;
92pub(crate) mod month_input_type;
93pub(crate) mod number_input_type;
94pub(crate) mod password_input_type;
95pub(crate) mod radio_input_type;
96pub(crate) mod range_input_type;
97pub(crate) mod reset_input_type;
98pub(crate) mod search_input_type;
99pub(crate) mod submit_input_type;
100pub(crate) mod tel_input_type;
101pub(crate) mod text_input_type;
102pub(crate) mod text_input_widget;
103pub(crate) mod text_value_widget;
104pub(crate) mod time_input_type;
105pub(crate) mod url_input_type;
106pub(crate) mod week_input_type;
107
108#[derive(Debug, PartialEq)]
109enum ValueMode {
110 Value,
112
113 Default,
115
116 DefaultOn,
118
119 Filename,
121}
122
123#[derive(Debug, PartialEq)]
124enum StepDirection {
125 Up,
126 Down,
127}
128
129#[dom_struct]
130pub(crate) struct HTMLInputElement {
131 htmlelement: HTMLElement,
132 input_type: DomRefCell<InputType>,
133
134 is_textual_or_password: Cell<bool>,
137
138 checked_changed: Cell<bool>,
140 placeholder: DomRefCell<DOMString>,
141 size: Cell<u32>,
142 maxlength: Cell<i32>,
143 minlength: Cell<i32>,
144 #[no_trace]
145 textinput: DomRefCell<TextInput<EmbedderClipboardProvider>>,
146 value_dirty: Cell<bool>,
148 #[no_trace]
151 #[conditional_malloc_size_of]
152 shared_selection: SharedSelection,
153
154 form_owner: MutNullableDom<HTMLFormElement>,
155 labels_node_list: MutNullableDom<NodeList>,
156 validity_state: MutNullableDom<ValidityState>,
157 #[no_trace]
158 pending_webdriver_response: RefCell<Option<PendingWebDriverResponse>>,
159}
160
161#[derive(JSTraceable)]
162pub(crate) struct InputActivationState {
163 indeterminate: bool,
164 checked: bool,
165 checked_radio: Option<DomRoot<HTMLInputElement>>,
166 was_radio: bool,
167 was_checkbox: bool,
168 }
170
171static DEFAULT_INPUT_SIZE: u32 = 20;
172static DEFAULT_MAX_LENGTH: i32 = -1;
173static DEFAULT_MIN_LENGTH: i32 = -1;
174
175#[expect(non_snake_case)]
176impl HTMLInputElement {
177 fn new_inherited(
178 local_name: LocalName,
179 prefix: Option<Prefix>,
180 document: &Document,
181 ) -> HTMLInputElement {
182 let embedder_sender = document
183 .window()
184 .as_global_scope()
185 .script_to_embedder_chan()
186 .clone();
187 HTMLInputElement {
188 htmlelement: HTMLElement::new_inherited_with_state(
189 ElementState::ENABLED | ElementState::READWRITE,
190 local_name,
191 prefix,
192 document,
193 ),
194 input_type: DomRefCell::new(InputType::new_text()),
195 is_textual_or_password: Cell::new(true),
196 placeholder: DomRefCell::new(DOMString::new()),
197 checked_changed: Cell::new(false),
198 maxlength: Cell::new(DEFAULT_MAX_LENGTH),
199 minlength: Cell::new(DEFAULT_MIN_LENGTH),
200 size: Cell::new(DEFAULT_INPUT_SIZE),
201 textinput: DomRefCell::new(TextInput::new(
202 Lines::Single,
203 DOMString::new(),
204 EmbedderClipboardProvider {
205 embedder_sender,
206 webview_id: document.webview_id(),
207 },
208 )),
209 value_dirty: Cell::new(false),
210 shared_selection: Default::default(),
211 form_owner: Default::default(),
212 labels_node_list: MutNullableDom::new(None),
213 validity_state: Default::default(),
214 pending_webdriver_response: Default::default(),
215 }
216 }
217
218 pub(crate) fn new(
219 cx: &mut js::context::JSContext,
220 local_name: LocalName,
221 prefix: Option<Prefix>,
222 document: &Document,
223 proto: Option<HandleObject>,
224 ) -> DomRoot<HTMLInputElement> {
225 Node::reflect_node_with_proto(
226 cx,
227 Box::new(HTMLInputElement::new_inherited(
228 local_name, prefix, document,
229 )),
230 document,
231 proto,
232 )
233 }
234
235 pub(crate) fn auto_directionality(&self) -> Option<String> {
236 match *self.input_type() {
237 InputType::Text(_) | InputType::Search(_) | InputType::Url(_) | InputType::Email(_) => {
238 let value: String = self.Value().to_string();
239 Some(HTMLInputElement::directionality_from_value(&value))
240 },
241 _ => None,
242 }
243 }
244
245 pub(crate) fn directionality_from_value(value: &str) -> String {
246 if HTMLInputElement::is_first_strong_character_rtl(value) {
247 "rtl".to_owned()
248 } else {
249 "ltr".to_owned()
250 }
251 }
252
253 fn is_first_strong_character_rtl(value: &str) -> bool {
254 for ch in value.chars() {
255 return match bidi_class(ch) {
256 BidiClass::L => false,
257 BidiClass::AL => true,
258 BidiClass::R => true,
259 _ => continue,
260 };
261 }
262 false
263 }
264
265 fn value_mode(&self) -> ValueMode {
268 match *self.input_type() {
269 InputType::Submit(_) |
270 InputType::Reset(_) |
271 InputType::Button(_) |
272 InputType::Image(_) |
273 InputType::Hidden(_) => ValueMode::Default,
274
275 InputType::Checkbox(_) | InputType::Radio(_) => ValueMode::DefaultOn,
276
277 InputType::Color(_) |
278 InputType::Date(_) |
279 InputType::DatetimeLocal(_) |
280 InputType::Email(_) |
281 InputType::Month(_) |
282 InputType::Number(_) |
283 InputType::Password(_) |
284 InputType::Range(_) |
285 InputType::Search(_) |
286 InputType::Tel(_) |
287 InputType::Text(_) |
288 InputType::Time(_) |
289 InputType::Url(_) |
290 InputType::Week(_) => ValueMode::Value,
291
292 InputType::File(_) => ValueMode::Filename,
293 }
294 }
295
296 #[inline]
297 pub(crate) fn input_type(&self) -> Ref<'_, InputType> {
298 self.input_type.borrow()
299 }
300
301 pub(crate) fn is_nontypeable(&self) -> bool {
303 matches!(
304 *self.input_type(),
305 InputType::Button(_) |
306 InputType::Checkbox(_) |
307 InputType::Color(_) |
308 InputType::File(_) |
309 InputType::Hidden(_) |
310 InputType::Image(_) |
311 InputType::Radio(_) |
312 InputType::Range(_) |
313 InputType::Reset(_) |
314 InputType::Submit(_)
315 )
316 }
317
318 #[inline]
319 pub(crate) fn is_submit_button(&self) -> bool {
320 matches!(
321 *self.input_type(),
322 InputType::Submit(_) | InputType::Image(_)
323 )
324 }
325
326 fn does_minmaxlength_apply(&self) -> bool {
327 matches!(
328 *self.input_type(),
329 InputType::Text(_) |
330 InputType::Search(_) |
331 InputType::Url(_) |
332 InputType::Tel(_) |
333 InputType::Email(_) |
334 InputType::Password(_)
335 )
336 }
337
338 fn does_pattern_apply(&self) -> bool {
339 matches!(
340 *self.input_type(),
341 InputType::Text(_) |
342 InputType::Search(_) |
343 InputType::Url(_) |
344 InputType::Tel(_) |
345 InputType::Email(_) |
346 InputType::Password(_)
347 )
348 }
349
350 fn does_multiple_apply(&self) -> bool {
351 matches!(*self.input_type(), InputType::Email(_))
352 }
353
354 fn does_value_as_number_apply(&self) -> bool {
357 matches!(
358 *self.input_type(),
359 InputType::Date(_) |
360 InputType::Month(_) |
361 InputType::Week(_) |
362 InputType::Time(_) |
363 InputType::DatetimeLocal(_) |
364 InputType::Number(_) |
365 InputType::Range(_)
366 )
367 }
368
369 fn does_value_as_date_apply(&self) -> bool {
370 matches!(
371 *self.input_type(),
372 InputType::Date(_) | InputType::Month(_) | InputType::Week(_) | InputType::Time(_)
373 )
374 }
375
376 fn allowed_value_step(&self) -> Option<f64> {
378 let default_step = self.default_step()?;
381
382 let Some(attribute) = self.upcast::<Element>().get_attribute(&local_name!("step")) else {
385 return Some(default_step * self.step_scale_factor());
386 };
387
388 if attribute.value().eq_ignore_ascii_case("any") {
391 return None;
392 }
393
394 let Some(parsed_value) =
398 parse_floating_point_number(&attribute.value()).filter(|value| *value > 0.0)
399 else {
400 return Some(default_step * self.step_scale_factor());
401 };
402
403 Some(parsed_value * self.step_scale_factor())
407 }
408
409 fn minimum(&self) -> Option<f64> {
411 self.upcast::<Element>()
412 .get_attribute(&local_name!("min"))
413 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
414 .or_else(|| self.default_minimum())
415 }
416
417 fn maximum(&self) -> Option<f64> {
419 self.upcast::<Element>()
420 .get_attribute(&local_name!("max"))
421 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
422 .or_else(|| self.default_maximum())
423 }
424
425 fn stepped_minimum(&self) -> Option<f64> {
428 match (self.minimum(), self.allowed_value_step()) {
429 (Some(min), Some(allowed_step)) => {
430 let step_base = self.step_base();
431 let nsteps = (min - step_base) / allowed_step;
433 Some(step_base + (allowed_step * nsteps.ceil()))
435 },
436 (_, _) => None,
437 }
438 }
439
440 fn stepped_maximum(&self) -> Option<f64> {
443 match (self.maximum(), self.allowed_value_step()) {
444 (Some(max), Some(allowed_step)) => {
445 let step_base = self.step_base();
446 let nsteps = (max - step_base) / allowed_step;
448 Some(step_base + (allowed_step * nsteps.floor()))
450 },
451 (_, _) => None,
452 }
453 }
454
455 fn default_minimum(&self) -> Option<f64> {
457 match *self.input_type() {
458 InputType::Range(_) => Some(0.0),
459 _ => None,
460 }
461 }
462
463 fn default_maximum(&self) -> Option<f64> {
465 match *self.input_type() {
466 InputType::Range(_) => Some(100.0),
467 _ => None,
468 }
469 }
470
471 fn default_range_value(&self) -> f64 {
473 let min = self.minimum().unwrap_or(0.0);
474 let max = self.maximum().unwrap_or(100.0);
475 if max < min {
476 min
477 } else {
478 min + (max - min) * 0.5
479 }
480 }
481
482 fn default_step(&self) -> Option<f64> {
484 match *self.input_type() {
485 InputType::Date(_) => Some(1.0),
486 InputType::Month(_) => Some(1.0),
487 InputType::Week(_) => Some(1.0),
488 InputType::Time(_) => Some(60.0),
489 InputType::DatetimeLocal(_) => Some(60.0),
490 InputType::Number(_) => Some(1.0),
491 InputType::Range(_) => Some(1.0),
492 _ => None,
493 }
494 }
495
496 fn step_scale_factor(&self) -> f64 {
498 match *self.input_type() {
499 InputType::Date(_) => 86400000.0,
500 InputType::Month(_) => 1.0,
501 InputType::Week(_) => 604800000.0,
502 InputType::Time(_) => 1000.0,
503 InputType::DatetimeLocal(_) => 1000.0,
504 InputType::Number(_) => 1.0,
505 InputType::Range(_) => 1.0,
506 _ => unreachable!(),
507 }
508 }
509
510 fn step_base(&self) -> f64 {
512 if let Some(minimum) = self
516 .upcast::<Element>()
517 .get_attribute(&local_name!("min"))
518 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
519 {
520 return minimum;
521 }
522
523 if let Some(value) = self
527 .upcast::<Element>()
528 .get_attribute(&local_name!("value"))
529 .and_then(|attribute| self.convert_string_to_number(&attribute.value()))
530 {
531 return value;
532 }
533
534 if let Some(default_step_base) = self.default_step_base() {
536 return default_step_base;
537 }
538
539 0.0
541 }
542
543 fn default_step_base(&self) -> Option<f64> {
545 match *self.input_type() {
546 InputType::Week(_) => Some(-259200000.0),
547 _ => None,
548 }
549 }
550
551 fn step_up_or_down(&self, n: i32, dir: StepDirection, can_gc: CanGc) -> ErrorResult {
555 if !self.does_value_as_number_apply() {
558 return Err(Error::InvalidState(None));
559 }
560 let step_base = self.step_base();
561
562 let Some(allowed_value_step) = self.allowed_value_step() else {
564 return Err(Error::InvalidState(None));
565 };
566
567 let minimum = self.minimum();
570 let maximum = self.maximum();
571 if let (Some(min), Some(max)) = (minimum, maximum) {
572 if min > max {
573 return Ok(());
574 }
575
576 if let Some(stepped_minimum) = self.stepped_minimum() {
580 if stepped_minimum > max {
581 return Ok(());
582 }
583 }
584 }
585
586 let mut value: f64 = self
590 .convert_string_to_number(&self.Value().str())
591 .unwrap_or(0.0);
592
593 let valueBeforeStepping = value;
595
596 if (value - step_base) % allowed_value_step != 0.0 {
601 value = match dir {
602 StepDirection::Down =>
603 {
605 let intervals_from_base = ((value - step_base) / allowed_value_step).floor();
606 intervals_from_base * allowed_value_step + step_base
607 },
608 StepDirection::Up =>
609 {
611 let intervals_from_base = ((value - step_base) / allowed_value_step).ceil();
612 intervals_from_base * allowed_value_step + step_base
613 },
614 };
615 }
616 else {
618 value += match dir {
623 StepDirection::Down => -f64::from(n) * allowed_value_step,
624 StepDirection::Up => f64::from(n) * allowed_value_step,
625 };
626 }
627
628 if let Some(min) = minimum {
632 if value < min {
633 value = self.stepped_minimum().unwrap_or(value);
634 }
635 }
636
637 if let Some(max) = maximum {
641 if value > max {
642 value = self.stepped_maximum().unwrap_or(value);
643 }
644 }
645
646 match dir {
650 StepDirection::Down => {
651 if value > valueBeforeStepping {
652 return Ok(());
653 }
654 },
655 StepDirection::Up => {
656 if value < valueBeforeStepping {
657 return Ok(());
658 }
659 },
660 }
661
662 self.SetValueAsNumber(value, can_gc)
666 }
667
668 fn suggestions_source_element(&self) -> Option<DomRoot<HTMLDataListElement>> {
670 let list_string = self
671 .upcast::<Element>()
672 .get_string_attribute(&local_name!("list"));
673 if list_string.is_empty() {
674 return None;
675 }
676 let ancestor = self
677 .upcast::<Node>()
678 .GetRootNode(&GetRootNodeOptions::empty());
679 let first_with_id = &ancestor
680 .traverse_preorder(ShadowIncluding::No)
681 .find(|node| {
682 node.downcast::<Element>()
683 .is_some_and(|e| e.Id() == list_string)
684 });
685 first_with_id
686 .as_ref()
687 .and_then(|el| el.downcast::<HTMLDataListElement>())
688 .map(DomRoot::from_ref)
689 }
690
691 fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
693 self.input_type()
694 .as_specific()
695 .suffers_from_being_missing(self, value)
696 }
697
698 fn suffers_from_type_mismatch(&self, value: &DOMString) -> bool {
700 if value.is_empty() {
701 return false;
702 }
703
704 self.input_type()
705 .as_specific()
706 .suffers_from_type_mismatch(self, value)
707 }
708
709 fn suffers_from_pattern_mismatch(&self, value: &DOMString, can_gc: CanGc) -> bool {
711 let pattern_str = self.Pattern();
714 if value.is_empty() || pattern_str.is_empty() || !self.does_pattern_apply() {
715 return false;
716 }
717
718 let cx = GlobalScope::get_cx();
720 let _ac = enter_realm(self);
721 rooted!(in(*cx) let mut pattern = ptr::null_mut::<JSObject>());
722
723 if compile_pattern(cx, &pattern_str.str(), pattern.handle_mut(), can_gc) {
724 if self.Multiple() && self.does_multiple_apply() {
725 !split_commas(&value.str())
726 .all(|s| matches_js_regex(cx, pattern.handle(), s, can_gc).unwrap_or(true))
727 } else {
728 !matches_js_regex(cx, pattern.handle(), &value.str(), can_gc).unwrap_or(true)
729 }
730 } else {
731 false
733 }
734 }
735
736 fn suffers_from_bad_input(&self, value: &DOMString) -> bool {
738 if value.is_empty() {
739 return false;
740 }
741
742 self.input_type()
743 .as_specific()
744 .suffers_from_bad_input(value)
745 }
746
747 fn suffers_from_length_issues(&self, value: &DOMString) -> ValidationFlags {
750 let value_dirty = self.value_dirty.get();
753 let textinput = self.textinput.borrow();
754 let edit_by_user = !textinput.was_last_change_by_set_content();
755
756 if value.is_empty() || !value_dirty || !edit_by_user || !self.does_minmaxlength_apply() {
757 return ValidationFlags::empty();
758 }
759
760 let mut failed_flags = ValidationFlags::empty();
761 let Utf16CodeUnitLength(value_len) = textinput.len_utf16();
762 let min_length = self.MinLength();
763 let max_length = self.MaxLength();
764
765 if min_length != DEFAULT_MIN_LENGTH && value_len < (min_length as usize) {
766 failed_flags.insert(ValidationFlags::TOO_SHORT);
767 }
768
769 if max_length != DEFAULT_MAX_LENGTH && value_len > (max_length as usize) {
770 failed_flags.insert(ValidationFlags::TOO_LONG);
771 }
772
773 failed_flags
774 }
775
776 fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
780 if value.is_empty() || !self.does_value_as_number_apply() {
781 return ValidationFlags::empty();
782 }
783
784 let Some(value_as_number) = self.convert_string_to_number(&value.str()) else {
785 return ValidationFlags::empty();
786 };
787
788 let mut failed_flags = ValidationFlags::empty();
789 let min_value = self.minimum();
790 let max_value = self.maximum();
791
792 let has_reversed_range = match (min_value, max_value) {
794 (Some(min), Some(max)) => self.input_type().has_periodic_domain() && min > max,
795 _ => false,
796 };
797
798 if has_reversed_range {
799 if value_as_number > max_value.unwrap() && value_as_number < min_value.unwrap() {
801 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
802 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
803 }
804 } else {
805 if let Some(min_value) = min_value {
807 if value_as_number < min_value {
808 failed_flags.insert(ValidationFlags::RANGE_UNDERFLOW);
809 }
810 }
811 if let Some(max_value) = max_value {
813 if value_as_number > max_value {
814 failed_flags.insert(ValidationFlags::RANGE_OVERFLOW);
815 }
816 }
817 }
818
819 if let Some(step) = self.allowed_value_step() {
821 let diff = (self.step_base() - value_as_number) % step / value_as_number;
825 if diff.abs() > 1e-12 {
826 failed_flags.insert(ValidationFlags::STEP_MISMATCH);
827 }
828 }
829
830 failed_flags
831 }
832
833 pub(crate) fn is_textual_or_password(&self) -> bool {
835 self.is_textual_or_password.get()
836 }
837
838 fn may_have_embedder_control(&self) -> bool {
839 let el = self.upcast::<Element>();
840 matches!(*self.input_type(), InputType::Color(_)) && !el.disabled_state()
841 }
842
843 fn handle_key_reaction(&self, action: KeyReaction, event: &Event, can_gc: CanGc) {
844 match action {
845 KeyReaction::TriggerDefaultAction => {
846 self.implicit_submission(can_gc);
847 event.mark_as_handled();
848 },
849 KeyReaction::DispatchInput(text, is_composing, input_type) => {
850 if event.IsTrusted() {
851 self.textinput.borrow().queue_input_event(
852 self.upcast(),
853 text,
854 is_composing,
855 input_type,
856 );
857 }
858 self.value_dirty.set(true);
859 self.update_placeholder_shown_state();
860 self.upcast::<Node>().dirty(NodeDamage::Other);
861 event.mark_as_handled();
862 },
863 KeyReaction::RedrawSelection => {
864 self.maybe_update_shared_selection();
865 event.mark_as_handled();
866 },
867 KeyReaction::Nothing => (),
868 }
869 }
870
871 fn value_for_shadow_dom(&self) -> DOMString {
873 let input_type = &*self.input_type();
874 match input_type {
875 InputType::Checkbox(_) |
876 InputType::Radio(_) |
877 InputType::Image(_) |
878 InputType::Hidden(_) |
879 InputType::Range(_) => input_type.as_specific().value_for_shadow_dom(self),
880 _ => {
881 if let Some(attribute_value) = self
882 .upcast::<Element>()
883 .get_attribute(&local_name!("value"))
884 .map(|attribute| attribute.Value())
885 {
886 return attribute_value;
887 }
888 input_type.as_specific().value_for_shadow_dom(self)
889 },
890 }
891 }
892
893 fn textinput_mut(&self) -> RefMut<'_, TextInput<EmbedderClipboardProvider>> {
894 self.textinput.borrow_mut()
895 }
896}
897
898impl<'dom> LayoutDom<'dom, HTMLInputElement> {
899 pub(crate) fn size_for_layout(self) -> u32 {
909 self.unsafe_get().size.get()
910 }
911
912 pub(crate) fn selection_for_layout(self) -> Option<SharedSelection> {
913 if !self.unsafe_get().is_textual_or_password.get() {
914 return None;
915 }
916 Some(self.unsafe_get().shared_selection.clone())
917 }
918}
919
920impl TextControlElement for HTMLInputElement {
921 fn selection_api_applies(&self) -> bool {
923 matches!(
924 *self.input_type(),
925 InputType::Text(_) |
926 InputType::Search(_) |
927 InputType::Url(_) |
928 InputType::Tel(_) |
929 InputType::Password(_)
930 )
931 }
932
933 fn has_selectable_text(&self) -> bool {
941 self.is_textual_or_password() && !self.textinput.borrow().get_content().is_empty()
942 }
943
944 fn has_uncollapsed_selection(&self) -> bool {
945 self.textinput.borrow().has_uncollapsed_selection()
946 }
947
948 fn set_dirty_value_flag(&self, value: bool) {
949 self.value_dirty.set(value)
950 }
951
952 fn select_all(&self) {
953 self.textinput.borrow_mut().select_all();
954 self.maybe_update_shared_selection();
955 }
956
957 fn maybe_update_shared_selection(&self) {
958 let offsets = self.textinput.borrow().sorted_selection_offsets_range();
959 let (start, end) = (offsets.start.0, offsets.end.0);
960 let range = TextByteRange::new(ByteIndex(start), ByteIndex(end));
961 let enabled = self.is_textual_or_password() && self.upcast::<Element>().focus_state();
962
963 let mut shared_selection = self.shared_selection.borrow_mut();
964 if range == shared_selection.range && enabled == shared_selection.enabled {
965 return;
966 }
967
968 *shared_selection = ScriptSelection {
969 range,
970 character_range: self
971 .textinput
972 .borrow()
973 .sorted_selection_character_offsets_range(),
974 enabled,
975 };
976 self.owner_window().layout().set_needs_new_display_list();
977 }
978
979 fn is_password_field(&self) -> bool {
980 matches!(*self.input_type(), InputType::Password(_))
981 }
982
983 fn placeholder_text<'a>(&'a self) -> Ref<'a, DOMString> {
984 self.placeholder.borrow()
985 }
986
987 fn value_text(&self) -> DOMString {
988 self.Value()
989 }
990}
991
992impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
993 make_getter!(Accept, "accept");
995
996 make_setter!(SetAccept, "accept");
998
999 make_bool_getter!(Alpha, "alpha");
1001
1002 make_bool_setter!(SetAlpha, "alpha");
1004
1005 make_getter!(Alt, "alt");
1007
1008 make_setter!(SetAlt, "alt");
1010
1011 make_getter!(DirName, "dirname");
1013
1014 make_setter!(SetDirName, "dirname");
1016
1017 make_bool_getter!(Disabled, "disabled");
1019
1020 make_bool_setter!(SetDisabled, "disabled");
1022
1023 fn GetForm(&self) -> Option<DomRoot<HTMLFormElement>> {
1025 self.form_owner()
1026 }
1027
1028 fn GetFiles(&self) -> Option<DomRoot<FileList>> {
1030 self.input_type()
1031 .as_specific()
1032 .get_files()
1033 .as_ref()
1034 .cloned()
1035 }
1036
1037 fn SetFiles(&self, files: Option<&FileList>) {
1039 if let Some(files) = files {
1040 self.input_type().as_specific().set_files(files)
1041 }
1042 }
1043
1044 make_bool_getter!(DefaultChecked, "checked");
1046
1047 make_bool_setter!(SetDefaultChecked, "checked");
1049
1050 fn Checked(&self) -> bool {
1052 self.upcast::<Element>()
1053 .state()
1054 .contains(ElementState::CHECKED)
1055 }
1056
1057 fn SetChecked(&self, checked: bool, can_gc: CanGc) {
1059 self.update_checked_state(checked, true, can_gc);
1060 self.value_changed(can_gc);
1061 }
1062
1063 make_enumerated_getter!(
1065 ColorSpace,
1066 "colorspace",
1067 "limited-srgb" | "display-p3",
1068 missing => "limited-srgb",
1069 invalid => "limited-srgb"
1070 );
1071
1072 make_setter!(SetColorSpace, "colorspace");
1074
1075 make_bool_getter!(ReadOnly, "readonly");
1077
1078 make_bool_setter!(SetReadOnly, "readonly");
1080
1081 make_uint_getter!(Size, "size", DEFAULT_INPUT_SIZE);
1083
1084 make_limited_uint_setter!(SetSize, "size", DEFAULT_INPUT_SIZE);
1086
1087 fn Type(&self) -> DOMString {
1089 DOMString::from(self.input_type().as_str())
1090 }
1091
1092 make_atomic_setter!(SetType, "type");
1094
1095 fn Value(&self) -> DOMString {
1097 match self.value_mode() {
1098 ValueMode::Value => self.textinput.borrow().get_content(),
1099 ValueMode::Default => self
1100 .upcast::<Element>()
1101 .get_attribute(&local_name!("value"))
1102 .map_or(DOMString::from(""), |a| {
1103 DOMString::from(a.summarize().value)
1104 }),
1105 ValueMode::DefaultOn => self
1106 .upcast::<Element>()
1107 .get_attribute(&local_name!("value"))
1108 .map_or(DOMString::from("on"), |a| {
1109 DOMString::from(a.summarize().value)
1110 }),
1111 ValueMode::Filename => {
1112 let mut path = DOMString::from("");
1113 match self.input_type().as_specific().get_files() {
1114 Some(ref fl) => match fl.Item(0) {
1115 Some(ref f) => {
1116 path.push_str("C:\\fakepath\\");
1117 path.push_str(&f.name().str());
1118 path
1119 },
1120 None => path,
1121 },
1122 None => path,
1123 }
1124 },
1125 }
1126 }
1127
1128 fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult {
1130 match self.value_mode() {
1131 ValueMode::Value => {
1132 {
1133 self.value_dirty.set(true);
1135
1136 self.sanitize_value(&mut value);
1139
1140 let mut textinput = self.textinput.borrow_mut();
1141
1142 if textinput.get_content() != value {
1147 textinput.set_content(value);
1149
1150 textinput.clear_selection_to_end();
1151 }
1152 }
1153
1154 self.update_placeholder_shown_state();
1158 self.maybe_update_shared_selection();
1159 },
1160 ValueMode::Default | ValueMode::DefaultOn => {
1161 self.upcast::<Element>()
1162 .set_string_attribute(&local_name!("value"), value, can_gc);
1163 },
1164 ValueMode::Filename => {
1165 if value.is_empty() {
1166 let window = self.owner_window();
1167 let fl = FileList::new(&window, vec![], can_gc);
1168 self.input_type().as_specific().set_files(&fl)
1169 } else {
1170 return Err(Error::InvalidState(None));
1171 }
1172 },
1173 }
1174
1175 self.value_changed(can_gc);
1176 self.upcast::<Node>().dirty(NodeDamage::Other);
1177 Ok(())
1178 }
1179
1180 make_getter!(DefaultValue, "value");
1182
1183 make_setter!(SetDefaultValue, "value");
1185
1186 make_getter!(Min, "min");
1188
1189 make_setter!(SetMin, "min");
1191
1192 fn GetList(&self) -> Option<DomRoot<HTMLDataListElement>> {
1194 self.suggestions_source_element()
1195 }
1196
1197 #[expect(unsafe_code)]
1199 fn GetValueAsDate(&self, cx: SafeJSContext) -> Option<NonNull<JSObject>> {
1200 self.input_type()
1201 .as_specific()
1202 .convert_string_to_naive_datetime(self.Value())
1203 .map(|date_time| unsafe {
1204 let time = ClippedTime {
1205 t: (date_time - OffsetDateTime::UNIX_EPOCH).whole_milliseconds() as f64,
1206 };
1207 NonNull::new_unchecked(NewDateObject(*cx, time))
1208 })
1209 }
1210
1211 #[expect(non_snake_case)]
1213 #[expect(unsafe_code)]
1214 fn SetValueAsDate(
1215 &self,
1216 cx: SafeJSContext,
1217 value: *mut JSObject,
1218 can_gc: CanGc,
1219 ) -> ErrorResult {
1220 rooted!(in(*cx) let value = value);
1221 if !self.does_value_as_date_apply() {
1222 return Err(Error::InvalidState(None));
1223 }
1224 if value.is_null() {
1225 return self.SetValue(DOMString::from(""), can_gc);
1226 }
1227 let mut msecs: f64 = 0.0;
1228 unsafe {
1232 let mut isDate = false;
1233 if !ObjectIsDate(*cx, Handle::from(value.handle()), &mut isDate) {
1234 return Err(Error::JSFailed);
1235 }
1236 if !isDate {
1237 return Err(Error::Type(c"Value was not a date".to_owned()));
1238 }
1239 if !DateGetMsecSinceEpoch(*cx, Handle::from(value.handle()), &mut msecs) {
1240 return Err(Error::JSFailed);
1241 }
1242 if !msecs.is_finite() {
1243 return self.SetValue(DOMString::from(""), can_gc);
1244 }
1245 }
1246
1247 let Ok(date_time) = OffsetDateTime::from_unix_timestamp_nanos((msecs * 1e6) as i128) else {
1248 return self.SetValue(DOMString::from(""), can_gc);
1249 };
1250 self.SetValue(
1251 self.input_type()
1252 .as_specific()
1253 .convert_datetime_to_dom_string(date_time),
1254 can_gc,
1255 )
1256 }
1257
1258 fn ValueAsNumber(&self) -> f64 {
1260 self.convert_string_to_number(&self.Value().str())
1261 .unwrap_or(f64::NAN)
1262 }
1263
1264 fn SetValueAsNumber(&self, value: f64, can_gc: CanGc) -> ErrorResult {
1266 if value.is_infinite() {
1267 Err(Error::Type(c"value is not finite".to_owned()))
1268 } else if !self.does_value_as_number_apply() {
1269 Err(Error::InvalidState(None))
1270 } else if value.is_nan() {
1271 self.SetValue(DOMString::from(""), can_gc)
1272 } else if let Some(converted) = self.convert_number_to_string(value) {
1273 self.SetValue(converted, can_gc)
1274 } else {
1275 self.SetValue(DOMString::from(""), can_gc)
1280 }
1281 }
1282
1283 make_getter!(Name, "name");
1285
1286 make_atomic_setter!(SetName, "name");
1288
1289 make_getter!(Placeholder, "placeholder");
1291
1292 make_setter!(SetPlaceholder, "placeholder");
1294
1295 make_form_action_getter!(FormAction, "formaction");
1297
1298 make_setter!(SetFormAction, "formaction");
1300
1301 make_enumerated_getter!(
1303 FormEnctype,
1304 "formenctype",
1305 "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data",
1306 invalid => "application/x-www-form-urlencoded"
1307 );
1308
1309 make_setter!(SetFormEnctype, "formenctype");
1311
1312 make_enumerated_getter!(
1314 FormMethod,
1315 "formmethod",
1316 "get" | "post" | "dialog",
1317 invalid => "get"
1318 );
1319
1320 make_setter!(SetFormMethod, "formmethod");
1322
1323 make_getter!(FormTarget, "formtarget");
1325
1326 make_setter!(SetFormTarget, "formtarget");
1328
1329 make_bool_getter!(FormNoValidate, "formnovalidate");
1331
1332 make_bool_setter!(SetFormNoValidate, "formnovalidate");
1334
1335 make_getter!(Max, "max");
1337
1338 make_setter!(SetMax, "max");
1340
1341 make_int_getter!(MaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1343
1344 make_limited_int_setter!(SetMaxLength, "maxlength", DEFAULT_MAX_LENGTH);
1346
1347 make_int_getter!(MinLength, "minlength", DEFAULT_MIN_LENGTH);
1349
1350 make_limited_int_setter!(SetMinLength, "minlength", DEFAULT_MIN_LENGTH);
1352
1353 make_bool_getter!(Multiple, "multiple");
1355
1356 make_bool_setter!(SetMultiple, "multiple");
1358
1359 make_getter!(Pattern, "pattern");
1361
1362 make_setter!(SetPattern, "pattern");
1364
1365 make_bool_getter!(Required, "required");
1367
1368 make_bool_setter!(SetRequired, "required");
1370
1371 make_url_getter!(Src, "src");
1373
1374 make_url_setter!(SetSrc, "src");
1376
1377 make_getter!(Step, "step");
1379
1380 make_setter!(SetStep, "step");
1382
1383 make_getter!(UseMap, "usemap");
1385
1386 make_setter!(SetUseMap, "usemap");
1388
1389 fn Indeterminate(&self) -> bool {
1391 self.upcast::<Element>()
1392 .state()
1393 .contains(ElementState::INDETERMINATE)
1394 }
1395
1396 fn SetIndeterminate(&self, val: bool) {
1398 self.upcast::<Element>()
1399 .set_state(ElementState::INDETERMINATE, val)
1400 }
1401
1402 fn GetLabels(&self, can_gc: CanGc) -> Option<DomRoot<NodeList>> {
1406 if matches!(*self.input_type(), InputType::Hidden(_)) {
1407 None
1408 } else {
1409 Some(self.labels_node_list.or_init(|| {
1410 NodeList::new_labels_list(
1411 self.upcast::<Node>().owner_doc().window(),
1412 self.upcast::<HTMLElement>(),
1413 can_gc,
1414 )
1415 }))
1416 }
1417 }
1418
1419 fn Select(&self) {
1421 self.selection().dom_select();
1422 }
1423
1424 fn GetSelectionStart(&self) -> Option<u32> {
1426 self.selection().dom_start().map(|start| start.0 as u32)
1427 }
1428
1429 fn SetSelectionStart(&self, start: Option<u32>) -> ErrorResult {
1431 self.selection()
1432 .set_dom_start(start.map(Utf16CodeUnitLength::from))
1433 }
1434
1435 fn GetSelectionEnd(&self) -> Option<u32> {
1437 self.selection().dom_end().map(|end| end.0 as u32)
1438 }
1439
1440 fn SetSelectionEnd(&self, end: Option<u32>) -> ErrorResult {
1442 self.selection()
1443 .set_dom_end(end.map(Utf16CodeUnitLength::from))
1444 }
1445
1446 fn GetSelectionDirection(&self) -> Option<DOMString> {
1448 self.selection().dom_direction()
1449 }
1450
1451 fn SetSelectionDirection(&self, direction: Option<DOMString>) -> ErrorResult {
1453 self.selection().set_dom_direction(direction)
1454 }
1455
1456 fn SetSelectionRange(&self, start: u32, end: u32, direction: Option<DOMString>) -> ErrorResult {
1458 self.selection().set_dom_range(
1459 Utf16CodeUnitLength::from(start),
1460 Utf16CodeUnitLength::from(end),
1461 direction,
1462 )
1463 }
1464
1465 fn SetRangeText(&self, replacement: DOMString) -> ErrorResult {
1467 self.selection()
1468 .set_dom_range_text(replacement, None, None, Default::default())
1469 }
1470
1471 fn SetRangeText_(
1473 &self,
1474 replacement: DOMString,
1475 start: u32,
1476 end: u32,
1477 selection_mode: SelectionMode,
1478 ) -> ErrorResult {
1479 self.selection().set_dom_range_text(
1480 replacement,
1481 Some(Utf16CodeUnitLength::from(start)),
1482 Some(Utf16CodeUnitLength::from(end)),
1483 selection_mode,
1484 )
1485 }
1486
1487 fn SelectFiles(&self, paths: Vec<DOMString>) {
1490 self.input_type()
1491 .as_specific()
1492 .select_files(self, Some(paths));
1493 }
1494
1495 fn StepUp(&self, n: i32, can_gc: CanGc) -> ErrorResult {
1497 self.step_up_or_down(n, StepDirection::Up, can_gc)
1498 }
1499
1500 fn StepDown(&self, n: i32, can_gc: CanGc) -> ErrorResult {
1502 self.step_up_or_down(n, StepDirection::Down, can_gc)
1503 }
1504
1505 fn WillValidate(&self) -> bool {
1507 self.is_instance_validatable()
1508 }
1509
1510 fn Validity(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
1512 self.validity_state(can_gc)
1513 }
1514
1515 fn CheckValidity(&self, cx: &mut JSContext) -> bool {
1517 self.check_validity(cx)
1518 }
1519
1520 fn ReportValidity(&self, cx: &mut JSContext) -> bool {
1522 self.report_validity(cx)
1523 }
1524
1525 fn ValidationMessage(&self) -> DOMString {
1527 self.validation_message()
1528 }
1529
1530 fn SetCustomValidity(&self, error: DOMString, can_gc: CanGc) {
1532 self.validity_state(can_gc).set_custom_error_message(error);
1533 }
1534}
1535
1536impl HTMLInputElement {
1537 pub(crate) fn form_datums(
1540 &self,
1541 submitter: Option<FormSubmitterElement>,
1542 encoding: Option<&'static Encoding>,
1543 ) -> Vec<FormDatum> {
1544 let ty = self.Type();
1548
1549 let name = self.Name();
1551 let is_submitter = match submitter {
1552 Some(FormSubmitterElement::Input(s)) => self == s,
1553 _ => false,
1554 };
1555
1556 match *self.input_type() {
1557 InputType::Submit(_) | InputType::Button(_) | InputType::Reset(_) if !is_submitter => {
1559 return vec![];
1560 },
1561
1562 InputType::Radio(_) | InputType::Checkbox(_) => {
1564 if !self.Checked() || name.is_empty() {
1565 return vec![];
1566 }
1567 },
1568
1569 InputType::File(_) => {
1570 let mut datums = vec![];
1571
1572 let name = self.Name();
1574
1575 match self.GetFiles() {
1576 Some(fl) => {
1577 for f in fl.iter_files() {
1578 datums.push(FormDatum {
1579 ty: ty.clone(),
1580 name: name.clone(),
1581 value: FormDatumValue::File(DomRoot::from_ref(f)),
1582 });
1583 }
1584 },
1585 None => {
1586 datums.push(FormDatum {
1587 ty,
1590 name,
1591 value: FormDatumValue::String(DOMString::from("")),
1592 })
1593 },
1594 }
1595
1596 return datums;
1597 },
1598
1599 InputType::Image(_) => return vec![], InputType::Hidden(_) => {
1603 if name.to_ascii_lowercase() == "_charset_" {
1604 return vec![FormDatum {
1605 ty,
1606 name,
1607 value: FormDatumValue::String(match encoding {
1608 None => DOMString::from("UTF-8"),
1609 Some(enc) => DOMString::from(enc.name()),
1610 }),
1611 }];
1612 }
1613 },
1614
1615 _ => {
1617 if name.is_empty() {
1618 return vec![];
1619 }
1620 },
1621 }
1622
1623 vec![FormDatum {
1625 ty,
1626 name,
1627 value: FormDatumValue::String(self.Value()),
1628 }]
1629 }
1630
1631 fn radio_group_name(&self) -> Option<Atom> {
1633 self.upcast::<Element>()
1634 .get_name()
1635 .filter(|name| !name.is_empty())
1636 }
1637
1638 fn update_checked_state(&self, checked: bool, dirty: bool, can_gc: CanGc) {
1639 self.upcast::<Element>()
1640 .set_state(ElementState::CHECKED, checked);
1641
1642 if dirty {
1643 self.checked_changed.set(true);
1644 }
1645
1646 if matches!(*self.input_type(), InputType::Radio(_)) && checked {
1647 broadcast_radio_checked(self, self.radio_group_name().as_ref(), can_gc);
1648 }
1649
1650 self.upcast::<Node>().dirty(NodeDamage::Other);
1651 }
1652
1653 pub(crate) fn is_mutable(&self) -> bool {
1655 !(self.upcast::<Element>().disabled_state() || self.ReadOnly())
1658 }
1659
1660 pub(crate) fn reset(&self, can_gc: CanGc) {
1670 self.value_dirty.set(false);
1671
1672 let mut value = self.DefaultValue();
1674 self.sanitize_value(&mut value);
1675 self.textinput.borrow_mut().set_content(value);
1676
1677 let input_type = &*self.input_type();
1678 if matches!(input_type, InputType::Radio(_) | InputType::Checkbox(_)) {
1679 self.update_checked_state(self.DefaultChecked(), false, can_gc);
1680 self.checked_changed.set(false);
1681 }
1682
1683 if matches!(input_type, InputType::File(_)) {
1684 input_type.as_specific().set_files(&FileList::new(
1685 &self.owner_window(),
1686 vec![],
1687 can_gc,
1688 ));
1689 }
1690
1691 self.value_changed(can_gc);
1692 }
1693
1694 pub(crate) fn clear(&self, can_gc: CanGc) {
1697 self.value_dirty.set(false);
1699 self.checked_changed.set(false);
1700 self.textinput.borrow_mut().set_content(DOMString::from(""));
1702 self.update_checked_state(self.DefaultChecked(), false, can_gc);
1704 if self.input_type().as_specific().get_files().is_some() {
1706 let window = self.owner_window();
1707 let filelist = FileList::new(&window, vec![], can_gc);
1708 self.input_type().as_specific().set_files(&filelist);
1709 }
1710
1711 {
1714 let mut textinput = self.textinput.borrow_mut();
1715 let mut value = textinput.get_content();
1716 self.sanitize_value(&mut value);
1717 textinput.set_content(value);
1718 }
1719
1720 self.value_changed(can_gc);
1721 }
1722
1723 fn update_placeholder_shown_state(&self) {
1724 if !self.input_type().is_textual_or_password() {
1725 self.upcast::<Element>().set_placeholder_shown_state(false);
1726 } else {
1727 let has_placeholder = !self.placeholder.borrow().is_empty();
1728 let has_value = !self.textinput.borrow().is_empty();
1729 self.upcast::<Element>()
1730 .set_placeholder_shown_state(has_placeholder && !has_value);
1731 }
1732 }
1733
1734 pub(crate) fn select_files_for_webdriver(
1735 &self,
1736 test_paths: Vec<DOMString>,
1737 response_sender: GenericSender<Result<bool, ErrorStatus>>,
1738 ) {
1739 let mut stored_sender = self.pending_webdriver_response.borrow_mut();
1740 assert!(stored_sender.is_none());
1741
1742 *stored_sender = Some(PendingWebDriverResponse {
1743 response_sender,
1744 expected_file_count: test_paths.len(),
1745 });
1746
1747 self.input_type()
1748 .as_specific()
1749 .select_files(self, Some(test_paths));
1750 }
1751
1752 fn sanitize_value(&self, value: &mut DOMString) {
1754 self.input_type().as_specific().sanitize_value(self, value);
1755 }
1756
1757 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1758 fn selection(&self) -> TextControlSelection<'_, Self> {
1759 TextControlSelection::new(self, &self.textinput)
1760 }
1761
1762 fn implicit_submission(&self, can_gc: CanGc) {
1764 let doc = self.owner_document();
1765 let node = doc.upcast::<Node>();
1766 let owner = self.form_owner();
1767 let form = match owner {
1768 None => return,
1769 Some(ref f) => f,
1770 };
1771
1772 if self.upcast::<Element>().click_in_progress() {
1773 return;
1774 }
1775 let submit_button = node
1776 .traverse_preorder(ShadowIncluding::No)
1777 .filter_map(DomRoot::downcast::<HTMLInputElement>)
1778 .filter(|input| matches!(*input.input_type(), InputType::Submit(_)))
1779 .find(|r| r.form_owner() == owner);
1780 match submit_button {
1781 Some(ref button) => {
1782 if button.is_instance_activatable() {
1783 button
1786 .upcast::<Node>()
1787 .fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc);
1788 }
1789 },
1790 None => {
1791 let mut inputs = node
1792 .traverse_preorder(ShadowIncluding::No)
1793 .filter_map(DomRoot::downcast::<HTMLInputElement>)
1794 .filter(|input| {
1795 input.form_owner() == owner &&
1796 matches!(
1797 *input.input_type(),
1798 InputType::Text(_) |
1799 InputType::Search(_) |
1800 InputType::Url(_) |
1801 InputType::Tel(_) |
1802 InputType::Email(_) |
1803 InputType::Password(_) |
1804 InputType::Date(_) |
1805 InputType::Month(_) |
1806 InputType::Week(_) |
1807 InputType::Time(_) |
1808 InputType::DatetimeLocal(_) |
1809 InputType::Number(_)
1810 )
1811 });
1812
1813 if inputs.nth(1).is_some() {
1814 return;
1816 }
1817 form.submit(
1818 SubmittedFrom::NotFromForm,
1819 FormSubmitterElement::Form(form),
1820 can_gc,
1821 );
1822 },
1823 }
1824 }
1825
1826 fn convert_string_to_number(&self, value: &str) -> Option<f64> {
1828 self.input_type()
1829 .as_specific()
1830 .convert_string_to_number(value)
1831 }
1832
1833 fn convert_number_to_string(&self, value: f64) -> Option<DOMString> {
1835 self.input_type()
1836 .as_specific()
1837 .convert_number_to_string(value)
1838 }
1839
1840 fn update_related_validity_states(&self, can_gc: CanGc) {
1841 match *self.input_type() {
1842 InputType::Radio(_) => {
1843 perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc)
1844 },
1845 _ => {
1846 self.validity_state(can_gc)
1847 .perform_validation_and_update(ValidationFlags::all(), can_gc);
1848 },
1849 }
1850 }
1851
1852 #[expect(unsafe_code)]
1853 fn value_changed(&self, can_gc: CanGc) {
1854 self.maybe_update_shared_selection();
1855 self.update_related_validity_states(can_gc);
1856 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
1858 let cx = &mut cx;
1859 self.input_type().as_specific().update_shadow_tree(cx, self);
1860 }
1861
1862 fn show_the_picker_if_applicable(&self) {
1864 if !self.is_mutable() {
1868 return;
1869 }
1870
1871 self.input_type()
1874 .as_specific()
1875 .show_the_picker_if_applicable(self);
1876 }
1877
1878 pub(crate) fn handle_color_picker_response(&self, response: Option<RgbColor>, can_gc: CanGc) {
1879 if let InputType::Color(ref color_input_type) = *self.input_type() {
1880 color_input_type.handle_color_picker_response(self, response, can_gc)
1881 }
1882 }
1883
1884 pub(crate) fn handle_file_picker_response(
1885 &self,
1886 response: Option<Vec<SelectedFile>>,
1887 can_gc: CanGc,
1888 ) {
1889 if let InputType::File(ref file_input_type) = *self.input_type() {
1890 file_input_type.handle_file_picker_response(self, response, can_gc)
1891 }
1892 }
1893
1894 fn handle_focus_event(&self, event: &FocusEvent) {
1895 let event_type = event.upcast::<Event>().type_();
1896 if *event_type == *"blur" {
1897 self.owner_document()
1898 .embedder_controls()
1899 .hide_embedder_control(self.upcast());
1900 } else if *event_type == *"focus" {
1901 let input_type = &*self.input_type();
1902 let Ok(input_method_type) = input_type.try_into() else {
1903 return;
1904 };
1905
1906 self.owner_document()
1907 .embedder_controls()
1908 .show_embedder_control(
1909 ControlElement::Ime(DomRoot::from_ref(self.upcast())),
1910 EmbedderControlRequest::InputMethod(InputMethodRequest {
1911 input_method_type,
1912 text: self.Value().to_string(),
1913 insertion_point: self.GetSelectionEnd(),
1914 multiline: false,
1915 allow_virtual_keyboard: self.owner_window().has_sticky_activation(),
1917 }),
1918 None,
1919 );
1920 } else {
1921 unreachable!("Got unexpected FocusEvent {event_type:?}");
1922 }
1923 }
1924
1925 fn handle_mouse_event(&self, mouse_event: &MouseEvent) {
1926 if mouse_event.upcast::<Event>().DefaultPrevented() {
1927 return;
1928 }
1929
1930 if !self.input_type().is_textual_or_password() || self.textinput.borrow().is_empty() {
1933 return;
1934 }
1935 let node = self.upcast();
1936 if self
1937 .textinput
1938 .borrow_mut()
1939 .handle_mouse_event(node, mouse_event)
1940 {
1941 self.maybe_update_shared_selection();
1942 }
1943 }
1944}
1945
1946impl VirtualMethods for HTMLInputElement {
1947 fn super_type(&self) -> Option<&dyn VirtualMethods> {
1948 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
1949 }
1950
1951 fn attribute_mutated(&self, cx: &mut JSContext, attr: &Attr, mutation: AttributeMutation) {
1952 let could_have_had_embedder_control = self.may_have_embedder_control();
1953
1954 self.super_type()
1955 .unwrap()
1956 .attribute_mutated(cx, attr, mutation);
1957
1958 match *attr.local_name() {
1959 local_name!("disabled") => {
1960 let disabled_state = match mutation {
1961 AttributeMutation::Set(None, _) => true,
1962 AttributeMutation::Set(Some(_), _) => {
1963 return;
1965 },
1966 AttributeMutation::Removed => false,
1967 };
1968 let el = self.upcast::<Element>();
1969 el.set_disabled_state(disabled_state);
1970 el.set_enabled_state(!disabled_state);
1971 el.check_ancestors_disabled_state_for_form_control();
1972
1973 if self.input_type().is_textual() {
1974 let read_write = !(self.ReadOnly() || el.disabled_state());
1975 el.set_read_write_state(read_write);
1976 }
1977 },
1978 local_name!("checked") if !self.checked_changed.get() => {
1979 let checked_state = match mutation {
1980 AttributeMutation::Set(None, _) => true,
1981 AttributeMutation::Set(Some(_), _) => {
1982 return;
1984 },
1985 AttributeMutation::Removed => false,
1986 };
1987 self.update_checked_state(checked_state, false, CanGc::from_cx(cx));
1988 },
1989 local_name!("size") => {
1990 let size = mutation.new_value(attr).map(|value| value.as_uint());
1991 self.size.set(size.unwrap_or(DEFAULT_INPUT_SIZE));
1992 },
1993 local_name!("type") => {
1994 match mutation {
1995 AttributeMutation::Set(..) => {
1996 let (old_value_mode, old_idl_value) = (self.value_mode(), self.Value());
1998 let previously_selectable = self.selection_api_applies();
1999
2000 *self.input_type.borrow_mut() =
2001 InputType::new_from_atom(attr.value().as_atom());
2002 self.is_textual_or_password
2003 .set(self.input_type().is_textual_or_password());
2004
2005 let element = self.upcast::<Element>();
2006 if self.input_type().is_textual() {
2007 let read_write = !(self.ReadOnly() || element.disabled_state());
2008 element.set_read_write_state(read_write);
2009 } else {
2010 element.set_read_write_state(false);
2011 }
2012
2013 let new_value_mode = self.value_mode();
2014 match (&old_value_mode, old_idl_value.is_empty(), new_value_mode) {
2015 (&ValueMode::Value, false, ValueMode::Default) |
2017 (&ValueMode::Value, false, ValueMode::DefaultOn) => {
2018 self.SetValue(old_idl_value, CanGc::from_cx(cx))
2019 .expect("Failed to set input value on type change to a default ValueMode.");
2020 },
2021
2022 (_, _, ValueMode::Value) if old_value_mode != ValueMode::Value => {
2024 self.SetValue(
2025 self.upcast::<Element>()
2026 .get_attribute(&local_name!("value"))
2027 .map_or(DOMString::from(""), |a| {
2028 DOMString::from(a.summarize().value)
2029 }),
2030 CanGc::from_cx(cx),
2031 )
2032 .expect(
2033 "Failed to set input value on type change to ValueMode::Value.",
2034 );
2035 self.value_dirty.set(false);
2036 },
2037
2038 (_, _, ValueMode::Filename)
2040 if old_value_mode != ValueMode::Filename =>
2041 {
2042 self.SetValue(DOMString::from(""), CanGc::from_cx(cx))
2043 .expect("Failed to set input value on type change to ValueMode::Filename.");
2044 },
2045 _ => {},
2046 }
2047
2048 self.input_type()
2050 .as_specific()
2051 .signal_type_change(self, CanGc::from_cx(cx));
2052
2053 let mut textinput = self.textinput.borrow_mut();
2055 let mut value = textinput.get_content();
2056 self.sanitize_value(&mut value);
2057 textinput.set_content(value);
2058 self.upcast::<Node>().dirty(NodeDamage::Other);
2059
2060 if !previously_selectable && self.selection_api_applies() {
2062 textinput.clear_selection_to_start();
2063 }
2064 },
2065 AttributeMutation::Removed => {
2066 self.input_type()
2067 .as_specific()
2068 .signal_type_change(self, CanGc::from_cx(cx));
2069 *self.input_type.borrow_mut() = InputType::new_text();
2070 self.is_textual_or_password
2071 .set(self.input_type().is_textual_or_password());
2072
2073 let element = self.upcast::<Element>();
2074 let read_write = !(self.ReadOnly() || element.disabled_state());
2075 element.set_read_write_state(read_write);
2076 },
2077 }
2078
2079 self.update_placeholder_shown_state();
2080 self.input_type()
2081 .as_specific()
2082 .update_placeholder_contents(cx, self);
2083 },
2084 local_name!("value") if !self.value_dirty.get() => {
2085 let value = mutation.new_value(attr).map(|value| (**value).to_owned());
2089 let mut value = value.map_or(DOMString::new(), DOMString::from);
2090
2091 self.sanitize_value(&mut value);
2092 self.textinput.borrow_mut().set_content(value);
2093 self.update_placeholder_shown_state();
2094 },
2095 local_name!("maxlength") => match *attr.value() {
2096 AttrValue::Int(_, value) => {
2097 let mut textinput = self.textinput.borrow_mut();
2098
2099 if value < 0 {
2100 textinput.set_max_length(None);
2101 } else {
2102 textinput.set_max_length(Some(Utf16CodeUnitLength(value as usize)))
2103 }
2104 },
2105 _ => panic!("Expected an AttrValue::Int"),
2106 },
2107 local_name!("minlength") => match *attr.value() {
2108 AttrValue::Int(_, value) => {
2109 let mut textinput = self.textinput.borrow_mut();
2110
2111 if value < 0 {
2112 textinput.set_min_length(None);
2113 } else {
2114 textinput.set_min_length(Some(Utf16CodeUnitLength(value as usize)))
2115 }
2116 },
2117 _ => panic!("Expected an AttrValue::Int"),
2118 },
2119 local_name!("placeholder") => {
2120 {
2121 let mut placeholder = self.placeholder.borrow_mut();
2122 placeholder.clear();
2123 if let AttributeMutation::Set(..) = mutation {
2124 placeholder
2125 .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r'));
2126 }
2127 }
2128 self.update_placeholder_shown_state();
2129 self.input_type()
2130 .as_specific()
2131 .update_placeholder_contents(cx, self);
2132 },
2133 local_name!("readonly") => {
2134 if self.input_type().is_textual() {
2135 let el = self.upcast::<Element>();
2136 match mutation {
2137 AttributeMutation::Set(..) => {
2138 el.set_read_write_state(false);
2139 },
2140 AttributeMutation::Removed => {
2141 el.set_read_write_state(!el.disabled_state());
2142 },
2143 }
2144 }
2145 },
2146 local_name!("form") => {
2147 self.form_attribute_mutated(mutation, CanGc::from_cx(cx));
2148 },
2149 _ => {
2150 self.input_type()
2151 .as_specific()
2152 .attribute_mutated(cx, self, attr, mutation);
2153 },
2154 }
2155
2156 self.value_changed(CanGc::from_cx(cx));
2157
2158 if could_have_had_embedder_control && !self.may_have_embedder_control() {
2159 self.owner_document()
2160 .embedder_controls()
2161 .hide_embedder_control(self.upcast());
2162 }
2163 }
2164
2165 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
2166 match *name {
2167 local_name!("accept") => AttrValue::from_comma_separated_tokenlist(value.into()),
2168 local_name!("size") => AttrValue::from_limited_u32(value.into(), DEFAULT_INPUT_SIZE),
2169 local_name!("type") => AttrValue::from_atomic(value.into()),
2170 local_name!("maxlength") => {
2171 AttrValue::from_limited_i32(value.into(), DEFAULT_MAX_LENGTH)
2172 },
2173 local_name!("minlength") => {
2174 AttrValue::from_limited_i32(value.into(), DEFAULT_MIN_LENGTH)
2175 },
2176 _ => self
2177 .super_type()
2178 .unwrap()
2179 .parse_plain_attribute(name, value),
2180 }
2181 }
2182
2183 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
2184 if let Some(s) = self.super_type() {
2185 s.bind_to_tree(cx, context);
2186 }
2187 self.upcast::<Element>()
2188 .check_ancestors_disabled_state_for_form_control();
2189
2190 self.input_type()
2191 .as_specific()
2192 .bind_to_tree(cx, self, context);
2193
2194 self.value_changed(CanGc::from_cx(cx));
2195 }
2196
2197 fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
2198 let form_owner = self.form_owner();
2199 self.super_type().unwrap().unbind_from_tree(context, can_gc);
2200
2201 let node = self.upcast::<Node>();
2202 let el = self.upcast::<Element>();
2203 if node
2204 .ancestors()
2205 .any(|ancestor| ancestor.is::<HTMLFieldSetElement>())
2206 {
2207 el.check_ancestors_disabled_state_for_form_control();
2208 } else {
2209 el.check_disabled_attribute();
2210 }
2211
2212 self.input_type()
2213 .as_specific()
2214 .unbind_from_tree(self, form_owner, context, can_gc);
2215
2216 self.validity_state(can_gc)
2217 .perform_validation_and_update(ValidationFlags::all(), can_gc);
2218 }
2219
2220 fn handle_event(&self, event: &Event, can_gc: CanGc) {
2226 if let Some(mouse_event) = event.downcast::<MouseEvent>() {
2227 self.handle_mouse_event(mouse_event);
2228 event.mark_as_handled();
2229 } else if event.type_() == atom!("keydown") &&
2230 !event.DefaultPrevented() &&
2231 self.input_type().is_textual_or_password()
2232 {
2233 if let Some(keyevent) = event.downcast::<KeyboardEvent>() {
2234 let action = self.textinput.borrow_mut().handle_keydown(keyevent);
2237 self.handle_key_reaction(action, event, can_gc);
2238 }
2239 } else if (event.type_() == atom!("compositionstart") ||
2240 event.type_() == atom!("compositionupdate") ||
2241 event.type_() == atom!("compositionend")) &&
2242 self.input_type().is_textual_or_password()
2243 {
2244 if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
2245 if event.type_() == atom!("compositionend") {
2246 let action = self
2247 .textinput
2248 .borrow_mut()
2249 .handle_compositionend(compositionevent);
2250 self.handle_key_reaction(action, event, can_gc);
2251 self.upcast::<Node>().dirty(NodeDamage::Other);
2252 self.update_placeholder_shown_state();
2253 } else if event.type_() == atom!("compositionupdate") {
2254 let action = self
2255 .textinput
2256 .borrow_mut()
2257 .handle_compositionupdate(compositionevent);
2258 self.handle_key_reaction(action, event, can_gc);
2259 self.upcast::<Node>().dirty(NodeDamage::Other);
2260 self.update_placeholder_shown_state();
2261 } else if event.type_() == atom!("compositionstart") {
2262 self.update_placeholder_shown_state();
2264 }
2265 event.mark_as_handled();
2266 }
2267 } else if let Some(clipboard_event) = event.downcast::<ClipboardEvent>() {
2268 let reaction = self
2269 .textinput
2270 .borrow_mut()
2271 .handle_clipboard_event(clipboard_event);
2272 let flags = reaction.flags;
2273 if flags.contains(ClipboardEventFlags::FireClipboardChangedEvent) {
2274 self.owner_document().event_handler().fire_clipboard_event(
2275 None,
2276 ClipboardEventType::Change,
2277 can_gc,
2278 );
2279 }
2280 if flags.contains(ClipboardEventFlags::QueueInputEvent) {
2281 self.textinput.borrow().queue_input_event(
2282 self.upcast(),
2283 reaction.text,
2284 IsComposing::NotComposing,
2285 reaction.input_type,
2286 );
2287 }
2288 if !flags.is_empty() {
2289 event.mark_as_handled();
2290 self.upcast::<Node>().dirty(NodeDamage::ContentOrHeritage);
2291 }
2292 } else if let Some(event) = event.downcast::<FocusEvent>() {
2293 self.handle_focus_event(event)
2294 }
2295
2296 self.value_changed(can_gc);
2297
2298 if let Some(super_type) = self.super_type() {
2299 super_type.handle_event(event, can_gc);
2300 }
2301 }
2302
2303 fn cloning_steps(
2305 &self,
2306 cx: &mut JSContext,
2307 copy: &Node,
2308 maybe_doc: Option<&Document>,
2309 clone_children: CloneChildrenFlag,
2310 ) {
2311 if let Some(s) = self.super_type() {
2312 s.cloning_steps(cx, copy, maybe_doc, clone_children);
2313 }
2314 let elem = copy.downcast::<HTMLInputElement>().unwrap();
2315 elem.value_dirty.set(self.value_dirty.get());
2316 elem.checked_changed.set(self.checked_changed.get());
2317 elem.upcast::<Element>()
2318 .set_state(ElementState::CHECKED, self.Checked());
2319 elem.textinput
2320 .borrow_mut()
2321 .set_content(self.textinput.borrow().get_content());
2322 self.value_changed(CanGc::from_cx(cx));
2323 }
2324}
2325
2326impl FormControl for HTMLInputElement {
2327 fn form_owner(&self) -> Option<DomRoot<HTMLFormElement>> {
2328 self.form_owner.get()
2329 }
2330
2331 fn set_form_owner(&self, form: Option<&HTMLFormElement>) {
2332 self.form_owner.set(form);
2333 }
2334
2335 fn to_element(&self) -> &Element {
2336 self.upcast::<Element>()
2337 }
2338}
2339
2340impl Validatable for HTMLInputElement {
2341 fn as_element(&self) -> &Element {
2342 self.upcast()
2343 }
2344
2345 fn validity_state(&self, can_gc: CanGc) -> DomRoot<ValidityState> {
2346 self.validity_state
2347 .or_init(|| ValidityState::new(&self.owner_window(), self.upcast(), can_gc))
2348 }
2349
2350 fn is_instance_validatable(&self) -> bool {
2351 match *self.input_type() {
2358 InputType::Hidden(_) | InputType::Button(_) | InputType::Reset(_) => false,
2359 _ => {
2360 !(self.upcast::<Element>().disabled_state() ||
2361 self.ReadOnly() ||
2362 is_barred_by_datalist_ancestor(self.upcast()))
2363 },
2364 }
2365 }
2366
2367 fn perform_validation(
2368 &self,
2369 validate_flags: ValidationFlags,
2370 can_gc: CanGc,
2371 ) -> ValidationFlags {
2372 let mut failed_flags = ValidationFlags::empty();
2373 let value = self.Value();
2374
2375 if validate_flags.contains(ValidationFlags::VALUE_MISSING) &&
2376 self.suffers_from_being_missing(&value)
2377 {
2378 failed_flags.insert(ValidationFlags::VALUE_MISSING);
2379 }
2380
2381 if validate_flags.contains(ValidationFlags::TYPE_MISMATCH) &&
2382 self.suffers_from_type_mismatch(&value)
2383 {
2384 failed_flags.insert(ValidationFlags::TYPE_MISMATCH);
2385 }
2386
2387 if validate_flags.contains(ValidationFlags::PATTERN_MISMATCH) &&
2388 self.suffers_from_pattern_mismatch(&value, can_gc)
2389 {
2390 failed_flags.insert(ValidationFlags::PATTERN_MISMATCH);
2391 }
2392
2393 if validate_flags.contains(ValidationFlags::BAD_INPUT) &&
2394 self.suffers_from_bad_input(&value)
2395 {
2396 failed_flags.insert(ValidationFlags::BAD_INPUT);
2397 }
2398
2399 if validate_flags.intersects(ValidationFlags::TOO_LONG | ValidationFlags::TOO_SHORT) {
2400 failed_flags |= self.suffers_from_length_issues(&value);
2401 }
2402
2403 if validate_flags.intersects(
2404 ValidationFlags::RANGE_UNDERFLOW |
2405 ValidationFlags::RANGE_OVERFLOW |
2406 ValidationFlags::STEP_MISMATCH,
2407 ) {
2408 failed_flags |= self.suffers_from_range_issues(&value);
2409 }
2410
2411 failed_flags & validate_flags
2412 }
2413}
2414
2415impl Activatable for HTMLInputElement {
2416 fn as_element(&self) -> &Element {
2417 self.upcast()
2418 }
2419
2420 fn is_instance_activatable(&self) -> bool {
2421 match *self.input_type() {
2422 InputType::Submit(_) |
2429 InputType::Reset(_) |
2430 InputType::File(_) |
2431 InputType::Image(_) |
2432 InputType::Button(_) => self.is_mutable(),
2433 InputType::Checkbox(_) | InputType::Radio(_) | InputType::Color(_) => true,
2437 _ => false,
2438 }
2439 }
2440
2441 fn legacy_pre_activation_behavior(&self, can_gc: CanGc) -> Option<InputActivationState> {
2443 let activation_state = self
2444 .input_type()
2445 .as_specific()
2446 .legacy_pre_activation_behavior(self, can_gc);
2447
2448 if activation_state.is_some() {
2449 self.value_changed(can_gc);
2450 }
2451
2452 activation_state
2453 }
2454
2455 fn legacy_canceled_activation_behavior(
2457 &self,
2458 cache: Option<InputActivationState>,
2459 can_gc: CanGc,
2460 ) {
2461 let ty = self.input_type();
2463 let cache = match cache {
2464 Some(cache) => {
2465 if (cache.was_radio && !matches!(*ty, InputType::Radio(_))) ||
2466 (cache.was_checkbox && !matches!(*ty, InputType::Checkbox(_)))
2467 {
2468 return;
2471 }
2472 cache
2473 },
2474 None => {
2475 return;
2476 },
2477 };
2478
2479 ty.as_specific()
2481 .legacy_canceled_activation_behavior(self, cache, can_gc);
2482
2483 self.value_changed(can_gc);
2484 }
2485
2486 fn activation_behavior(&self, event: &Event, target: &EventTarget, can_gc: CanGc) {
2488 self.input_type()
2489 .as_specific()
2490 .activation_behavior(self, event, target, can_gc)
2491 }
2492}
2493
2494fn compile_pattern(
2498 cx: SafeJSContext,
2499 pattern_str: &str,
2500 out_regex: MutableHandleObject,
2501 can_gc: CanGc,
2502) -> bool {
2503 if check_js_regex_syntax(cx, pattern_str, can_gc) {
2505 let pattern_str = format!("^(?:{})$", pattern_str);
2507 let flags = RegExpFlags {
2508 flags_: RegExpFlag_UnicodeSets,
2509 };
2510 new_js_regex(cx, &pattern_str, flags, out_regex, can_gc)
2511 } else {
2512 false
2513 }
2514}
2515
2516#[expect(unsafe_code)]
2517fn check_js_regex_syntax(cx: SafeJSContext, pattern: &str, _can_gc: CanGc) -> bool {
2520 let pattern: Vec<u16> = pattern.encode_utf16().collect();
2521 unsafe {
2522 rooted!(in(*cx) let mut exception = UndefinedValue());
2523
2524 let valid = CheckRegExpSyntax(
2525 *cx,
2526 pattern.as_ptr(),
2527 pattern.len(),
2528 RegExpFlags {
2529 flags_: RegExpFlag_UnicodeSets,
2530 },
2531 exception.handle_mut(),
2532 );
2533
2534 if !valid {
2535 JS_ClearPendingException(*cx);
2536 return false;
2537 }
2538
2539 exception.is_undefined()
2542 }
2543}
2544
2545#[expect(unsafe_code)]
2546pub(crate) fn new_js_regex(
2547 cx: SafeJSContext,
2548 pattern: &str,
2549 flags: RegExpFlags,
2550 mut out_regex: MutableHandleObject,
2551 _can_gc: CanGc,
2552) -> bool {
2553 let pattern: Vec<u16> = pattern.encode_utf16().collect();
2554 unsafe {
2555 out_regex.set(NewUCRegExpObject(
2556 *cx,
2557 pattern.as_ptr(),
2558 pattern.len(),
2559 flags,
2560 ));
2561 if out_regex.is_null() {
2562 JS_ClearPendingException(*cx);
2563 return false;
2564 }
2565 }
2566 true
2567}
2568
2569#[expect(unsafe_code)]
2570fn matches_js_regex(
2571 cx: SafeJSContext,
2572 regex_obj: HandleObject,
2573 value: &str,
2574 _can_gc: CanGc,
2575) -> Result<bool, ()> {
2576 let mut value: Vec<u16> = value.encode_utf16().collect();
2577
2578 unsafe {
2579 let mut is_regex = false;
2580 assert!(ObjectIsRegExp(*cx, regex_obj, &mut is_regex));
2581 assert!(is_regex);
2582
2583 rooted!(in(*cx) let mut rval = UndefinedValue());
2584 let mut index = 0;
2585
2586 let ok = ExecuteRegExpNoStatics(
2587 *cx,
2588 regex_obj,
2589 value.as_mut_ptr(),
2590 value.len(),
2591 &mut index,
2592 true,
2593 rval.handle_mut(),
2594 );
2595
2596 if ok {
2597 Ok(!rval.is_null())
2598 } else {
2599 JS_ClearPendingException(*cx);
2600 Err(())
2601 }
2602 }
2603}
2604
2605#[derive(MallocSizeOf)]
2609struct PendingWebDriverResponse {
2610 response_sender: GenericSender<Result<bool, ErrorStatus>>,
2612 expected_file_count: usize,
2614}
2615
2616impl PendingWebDriverResponse {
2617 fn finish(self, number_files_selected: usize) {
2618 if number_files_selected == self.expected_file_count {
2619 let _ = self.response_sender.send(Ok(false));
2620 } else {
2621 let _ = self.response_sender.send(Err(ErrorStatus::InvalidArgument));
2624 }
2625 }
2626}